Swift Class vs. Struct
This article discusses the differences between classes and structs in Swift. If you want to read about when and how to use these both I highly recommend you to go through swift official documentation.
1. Reference & Value type
Structs are value types whereas classes are reference types, i.e. every class instance can have multiple references.
To handle these multiple references ARC comes into play. To read more about ARC, you can refer to the below article.
2. De-init
As class instances can have multiple references and won’t get de-allocated till the reference count is zero, we developer may need to do some cleanup when it happens. To get this call back we have a deinit
function for classes.
Whereas struct instances get de-allocated when the instance gets out of the scope of its executing block. So we won't need an extra callback and because of that swift doesn't have a deinit for structs. (It does have other reasons too)
3. mutable vs immutable
Structs are truly immutable. That means every mutation copies every single member variable.
To see this in action we took the below example.
struct C {
var q: Int
init() {
self.q = 0
print("c is being initialized")
}
}
class B {
var c: C {
didSet {
print("c updated")
}
}
init(c: C) {
self.c = c
print("B is intialized")
}
}
var b = B(c: C())
b.c.q = 6
// prints
// c is being initalized
// B is intialized
// c updated
Even though the actual modification of the value in memory likely happens in-place, at a semantic level the entire C struct is being replaced with an entirely new value. That is the reason mutation is not allowed on let
struct instances
Consider reading this forum for more detailed context.
Whereas classes member variable can be mutable even if the reference is a let
(i.e. constant).
class SomeClass {
var a: Int
init(a: Int) {
self.a = a
}
}
let s = SomeClass(a: 7)
print(s.a)
s.a = 8
print(s.a)
// prints
// 7
// 8
4. Stack vs heap
Structs are value types so their sizes can be determined in the compile time and can be allocated in the call stack memory.
Class being reference type the sizes need to be calculated in the runtime, so Swift uses the memory heap for the dynamic memory allocation.
That’s the reason structs are fast compared to class.
Read more about swift memory management
5. Inheritance and conformance
Classes support Inheritance of other classes, though it doesn’t support multiple inheritance for a good reason. They can conform to protocols.
Structs on the other hand don't support inheritance, but they can also conform to protocol.
6. Static vs. Dynamic dispatch
Inheritance & method overriding is the reason why classes need dynamic dispatch.
By default, methods of classes are subject to dynamic dispatch. When you define a class method, it can be overridden by subclasses, and the method implementation to call is determined at runtime based on the actual subclass type.
Methods of structs are typically statically dispatched because structs are value types, and their methods are resolved based on the compile-time type of the variable. Since struct methods cannot be overridden, there’s no need for dynamic dispatch.
7. Type casting
Classes support inheritance. So a reference type of super class can contain subclass instances. We as a developer most of the time will need to check the type of the instance rather than the reference in the runtime.
Swift provides as, is, as?, as!
operators to do these type-casting in the runtime.
Structs on the other hand don’t need runtime type-casting.
8. Working with closure
If you already know closure then you must know that closure are reference type and the reference types need to be taken care of if we are working with classes. Otherwise, we’ll end up with retain cycles.
class SomeClass {
var a = 8
func doSomething() {
DispatchQueue.main.async {
print(self.a)
}
}
}
struct SomeStruct {
var a = 8
func doSomething() {
DispatchQueue.main.async {
print(a)
}
}
}
In the above example, you can see that I had to explicitly use self
the class member method inside the closure but in Struct, I did not.
Closure inside Struct can implicitly capture Swift, as the compiler doesn’t need to worry about ARC, but in the case of class, we have to explicitly use self
and define the reference type (weak, unowned, or strong) as per our requirement.
The above example uses
@escaping
closure, which is why we needed to useself
. But in the case of non-escaping closure, the life cycle of closure will be confined to the function life cycle. The closure will get freed up from memory when thedoSomething
returns. So we don’t need to worry about ARC, and we don’t have to explicitly mentionself
even if it is inside a class.
9. Equality(==) & Identity(===) operator
Equality(==) works for struct and classes that conform to Equatable
protocol. It tells if two instances are equal or not.
Wherein Identity(===) works only for instance of a class or class-constrained type(AnyObject). It checks if two reference points to the same memory location.