The myth about value and reference types

Remember myself trying to learn how iOS memory management works, few articles from the first pages of Google learned me that classes are deallocated by ARC when their reference count equal 0, value types like structs, arrays, or strings are saved on the stack, reference values like classes and closures go into the heap, value types are better because stack allocation is faster, etc. And then thought to myself… ok! I got it! But in reality, is not that simple.

Let’s see Apple’s definition of value and reference type:

Value type – A value type is a type whose value is copied when it’s assigned to a variable or constant, or when it’s passed to a function.

Reference type – Unlike value types, reference types are not copied when they’re assigned to a variable or constant, or when they’re passed to a function. Rather than a copy, a reference to the same existing instance is used.

As we can see those definitions only specify how types are passed around, there is not a single word about the place they will be stored, they can be stored on the stack or the heap or even ‘both’, this is just an implementation detail.

There are some cases when a value type is passed by reference, to accomplish additional functionality for a Swift language or just as an optimization, there are two well known cases:

1. Passing value type into a closure:

				
					 var name: String = "Bob"

DispatchQueue.global().asyncAfter(deadline: .now() + 1) {
    print(name) // prints "Muniek" - name variable is captured be a reference not a copy.
}

name = "Muniek"
				
			

Why? This is just how Swift was implemented, be default closure capture value by reference, or better said, capture variable by reference, you can use capture list to change that. 

This is one of Swift’s creators explaining why it works that way:

2 . Array is a value type but passed by reference not a copy, an actual copy of an array is made when mutation of this array occurs, this is an optimization to reduce copying large amounts of data as arrays can have thousands of items inside. This optimization is called Copy on write (COW). Underneath it behaves like a reference type violating the definition of value type but when you want to edit it, it will make a copy. Maybe that is the reason why Apple likes to refer to value types as „value type SEMANTICS” – they behave as value type on the surface, but what is underneath is again… implementation detail.

 

When type is allocated on the heap and when on the stack?

I’m sure you heard that stack allocation and deallocation are fast so the best precise will be to allocate everything on the stack, but it is impossible because of the way the stack works.

Generally, two things imply whether something is allocated on the heap:

1. when something has to outlive the scope it was created in
You can only put and pop from the stack, imagine a variable created inside some function. If this variable (THIS variable instance not a copy) has to be accessible after the function exits, it cannot be stored on the stack, because there will be other function-related stuff saved, so to deallocate function call and save THIS variable, the compiler will need to pop all function data and push THIS variable again on top, which may not seem like a lot of work but image function calling another function and this function calls another and another and every single function want to have some local variables by accessible outside their scope, this will require a lot of stack memory juggling – it is just not practical so better way is to allocate this variable on the heap and pass a reference to it.

2. when something may change its size
Imagine a function that has two local variables: String and Int

				
					func someFunction() {
   var name: String = "Muniek"
   var age: Int = 30
}

				
			

Runtime compiler will allocate memory for those variables on the stack like so:

(This is just a simplified demonstration, in reality, the whole name will be allocated in a single line/word)

Then if the function wants to change the name variable for something longer, again, the stack will have to pop all data up to the „name” variable, push a new string and other values again – which is not a quick way.

To be more precise, the compiler allocates some fixed amount of memory for a string variable, whether it is a single letter „A” or a name „Michael” String will take the same amount of memory on the stack, so some mutation is possible on the stack, but when string becomes longer, the whole string is moved into the heap and only reference to this memory is kept o the stack. Similarly, arrays that are value type but values inside an array are kept on the heap.

So if those two cases don’t occur, everything is allocated on the stack, right? What about class? If we create an immutable instance of a simple class, will it be stored on the stack?

				
					class Person {
    let name: String = "Muniek"
}

func someFunction() {
   let person = Person() // heap
}
				
			

It will be allocated on the heap. Why not on the stack? It will be really quick and just better, I do not know the answer, maybe Swift will implement this type of optimization in the future or maybe that type of optimization will prevent some other optimizations and they choose not to do it?

 

Reference type inside value type

Swift likes value types like structs, they are most likely stored on the stack and do not have shared instance which Apple always refer to as a cause of „unintended sharing of state”

So are structs always a better choice? Not really… just look at the example with arrays and COW we discussed earlier or this struct which contains a bunch of classes as members:

				
					struct SomeType {
    var age: Int

    var object: SomeClass
    var object: SomeClass
    var object: SomeClass
    var object: SomeClass
    var object: SomeClass
}
				
			

Struct being value type is passed as a copy but what about reference types inside? They will be passed as a reference, or better said copy of a reference increasing the reference count of every single one of them. Every time we pass this struct compiler needs to increase the reference count, which again… doesn’t look like much work, but on a larger scale it may sum up to milliseconds or even seconds! 

Let’s change SomeType to be a class – every time you pass this object only one reference count needs to be increased, there is no need to change the reference count of every reference type member.

 

Value type in a reference type

				
					struct SomeStruct {
    var name: String
}
        
class SomeClass {
    var someStruct: SomeStruct = .init(name: "Muniek")
}
				
			

In this situation, the value type struct will be saved where the class is, on the heap. Passing SomeClass to another function will pass a reference to this class and give access to the same struct:

				
					let someClass = SomeClass()
print(someClass.someStruct.name) // Muniek
        
func rename(_ someClass: SomeClass) {
   someClass.someStruct.name = "Bob"
}
        
rename(someClass)
print(someClass.someStruct.name) // Bob