Swift brings us classes and structs which both can look quite similar. When should you use a struct and when should you go for a class?
Cannot assign to property: function call returns immutable value
Transform Your Career with the iOS Lead Essentials — Black Friday Offer This Black Friday, unlock over 40 hours of expert training, mentorship, and community support to secure your place among the best devs. Click for early access to this limited offer.
You’re probably not the first to just simply fallback to changing your type to a class when an error like the above occurs. Structs tend to be harder to work with while it should be easy. Hopefully, after reading this blog post, you’ll be able to work with structs more easily!
What is a class in Swift?
A class in Swift is a reference type which can contain:
- properties
- methods
- subscripts
- initializers
- protocol conformances
- extensions
It’s often described as a template definition of an object, like in the following Article instance definition:
class ArticleClass {
let title: String
let url: URL
var readCount: Int = 0
init(title: String, url: URL) {
self.title = title
self.url = url
}
}
What is a struct in Swift?
A struct in Swift is a value type which, just like classes, can contain:
- properties
- methods
- subscripts
- initializers
- protocol conformances
- extensions
It can also be seen as a template definition of an object, like in the following Article instance definition:
struct ArticleStruct {
let title: String
let url: URL
var readCount: Int = 0
}
What are the differences between a struct and a class?
Yes, you’re right, the above examples looked almost the same! That’s the whole issue with structs and classes, they’re pretty similar. Still, there’s quite a lot of important differences to be aware of.
Value vs reference types
One of the most important differences is that a struct is a value type while a class is a reference type. Although this could be a blog post on its own, a short explanation should be enough to understand this difference.
References to a class instance share single data which means that any changes in that class will be available to each reference. This is shown in the following code example:
let articleClass = ArticleClass(title: "Struct vs Class", url: URL(string: "www.avanderlee.com")!)
let articleClassCopy = articleClass
articleClass.readCount = 10
print(articleClassCopy.readCount) // Prints: 10
A struct is a value type and will create a unique copy for each new reference. Taking the above code example shows this important difference as the read count is only updated on the referencing instance:
var articleStruct = ArticleStruct(title: "Struct vs Class", url: URL(string: "www.avanderlee.com")!, readCount: 0)
var articleStructCopy = articleStruct
articleStruct.readCount = 10
print(articleStructCopy.readCount) // Prints: 0
The benefit of mutation in safety
With this, structs have the benefit of mutation in safety as you can trust that no other part of your app is changing the data at the same time. This makes it easier to reason about your code and is especially helpful in multi-threaded environments where a different thread could alter your data at the same time. This could create nasty bugs which are hard to debug.
In the absence of mutation, both classes, and structs act exactly the same and the benefit of mutation does no longer count.
Structs and constants
Another related difference to value types is the use of constants. If you were sharp you could see that the articleStruct
was defined as a variable instead of a let constant like we did with the articleClass
. The following error probably looks familiar to you:
A struct can only be mutated if it’s defined as a variable and it will only update the referencing instance.
Structs get an initializer for free
If you go back and compare the above code examples you can see that the ArticleClass
has a defined initializer which is required for classes. Structs, however, get an initializer for free.
This gets even better with SE-242 which is implemented in Swift 5.1 and adds memberwise initializers for structs. This means the following difference:
// Before Swift 5.1 Memberwise initializers:
// Generated memberwise init: init(title: String, url: URL, readCount: Int)
let article = ArticleStruct(title: "", url: URL(string: "")!, readCount: 0)
// After Swift 5.1 Memberwise initializers, using the default 0 for read count
// Generated memberwise init: init(title: String, url: URL, readCount: Int = 0)
let article = ArticleStruct(title: "", url: URL(string: "")!)
Classes allow inheritance
Classes can inherit the characteristics of another class and with that, act like abstract classes. A common example is a custom view controller which inherit from UIViewController
.
With protocols in Swift, this is often no longer needed and replaceable with protocols. Protocols can be used with both classes and structs while inheritance is only possible with classes.
Classes can be deinitialized
A class allows executing code just before it gets destroyed by using a deinit method. When you define the same deinit
method in a struct you’ll get the following error:
Deinitializers may only be declared within a class
So, when should I go for a struct and when for a class?
The Swift documentation describes the decision as followed:
The additional capabilities that classes support come at the cost of increased complexity. As a general guideline, prefer structures because they’re easier to reason about, and use classes when they’re appropriate or necessary. In practice, this means most of the custom data types you define will be structures and enumerations.
This explains most of the topics we discussed above. Also, when working with Cocoa classes, you’re often required to subclass from NSObject
which requires you to use a class.
A simple bullet point list will make it a lot easier to decide.
You should use a class when:
- Comparing instance identity is needed by using
===
- Shared mutable state is required
- Objective-C interoperability is required
You should use a struct when:
- Comparing instance data is needed by using
==
- Unique copies with an independent state are required
- The data is used in multiple threads
Any golden tips you can give?
Yes, I can! Try to go for a struct by default. Structs make your code easier to reason about and make it easier to work in multithreaded environments which we often have while developing in Swift.
Also, if you do decide to go for a class, consider to mark it as final
and help the compiler by telling it that there are no other classes that inherit from your defined class.
Conclusion
Hopefully, you’ll be able to easier reason about choosing between a class or a struct. It’s definitely not always easy and possible to go for a struct but it should be considered the default when programming in Swift. Once you’ll use structs more often I’m pretty sure you’ll get used to working with them.
If you find yourself struggling often with decisions, you might be interested in the following posts as well:
Thanks!