Struct, Bool, Int - Stack
Class, function, closure - Heap Stack Allocation
is a simple and fast way to allocate/deallocate memory that involves the stack.
Each "scope" in your app (like the inner contents of a method) will provide the amount of memory it needs to run, move the stack pointer in memory by such amount and run - adding data to the empty memory addresses it now posesses. After the scope is lost, the stack pointer will simply decrease by the same amount - safely deallocating all the scope's data. The cost of allocating/deallocating stack memory is literally the cost of assigning an integer.
In Stack Allocation, the data gathered by the scope means everything attributed to it - like method arguments, return values, but more importantly: value types
. As long as the size of the value type is known during compile time and it doesn't contain / is not contained, recursively, by a reference type, it will not need reference counting
, and its life will be static
- equal to the life of its scope. It will be allocated completely on the stack, and when the scope is deallocated, so is the value type. The absence of reference counting overhead and the presence of stack allocation is a considerable performance boost.
If the contents of your value type are other stack allocated, static size value types, then your value type is also static sized. That means your value type will also leverage full stack allocation as well as having a performance boost in copying operations.
Assigning a property to most value types will indeed create a full copy of the object. However, this copy-on-assignment behaviour for fully stack allocated value types is so fast and so cheap that Apple claims it runs in constant time: Heap Allocation The stack is not meant to be used with objects that change in size, and the concept of pointers / dynamic lifetime means that an object's life could have nothing to do with its scope
- after all, it's possible to have an object existing in memory even if there's nothing going on. In this case, it's meant to be used for dynamically-allocated, user-managed memory.
When the process requests a certain amount of memory, the heap will search for a memory address that fulfills this request and return it to the process. When the memory is not being used anymore, the process must tell the heap to free that section of memory.
In iOS, "not being used anymore" works in the shape of reference counting, and luckily the existence of ARC means that most things are automatically handled for you unless you have to deal with the RawPointer family
Heap Allocation is slower than Stack Allocation not just because of the more complex data structure - it also requires thread safety. Each thread has its own stack, but the heap is shared with everybody, demanding synchronization. However, it allows reference types and things like dynamic size arrays to exist
It would be nice if memory management was as binary as saying that value types go to the stack and reference types go to the heap, but in reality, the life and performance of a value type is heavily defined by its contents. Heap Allocated Value Types
If the size of your value type cannot be determined during compile time (because of a protocol/generic requirement), or if your value type recursively contains / is contained by a reference type (remember that closures are also reference types), then it will require heap allocation. This can range from being not being an issue at all to making your structperform exponentially worse
than it would if it was a class instead.
Stack allocated value types are great because their life are directly related to their scope's life, but if your value type is the child of a class, a reference is all it takes for it to outlive its scope. This situation is common in @escaping closures, and this value type will lose its stack allocation properties in order to be fully heap allocated
alongside the reference type. In a way, you could even say that this kind of value type is a reference type itself
, as living in the heap means that several objects can point to it - even though it still possesses value semantics
If your value type is instead the parent of a heap allocated class, then it will not be heap allocated itself, but it will
inherit reference counting overhead in order to be able to keep it's inner references alive. This can cause a considerable drop in performance depending on the complexity of the value type.
In the Standard Library, examples of value types with child references are String, Array, Dictionary and Set. These value types contain internal reference types that manage the storage of elements in the heap, allowing them to increase/decrease in size as needed.
Since heap operations are more expensive than stack ones, copying heap allocated value types is not a constant operation like in stack allocated ones. To prevent this from hurting performance, the Standard Library's extensible data structures are copy-on-write
With this capability, merely assigning properties will not copy the value type - instead, it will create a reference just like if it was a regular reference type. The actual copying will only happen when it's really necessary.