Swift Class vs. Struct

Ajaya Mati
4 min readSep 17, 2023

--

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 use self. 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 the doSomething returns. So we don’t need to worry about ARC, and we don’t have to explicitly mention self 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.

--

--

Ajaya Mati

iOS Engineer@PhonePe, IITR@2022 | Swift, UIKit, Swift UI