Understanding Swift Copy-on-Write mechanisms

In Swift, we have reference types(Classes) and value types (Structs, Tuples, enums). The value types have a copy semantic. That means if you assign a value type to a variable or pass it as a parameter to a function(unless it is an inout parameter), the underlying data of this value is going to be copied. You will have two values with the same content but, allocated in two distinct memory addresses. For a more detailed explanation about the difference between reference and value types on Apple’s Blog.

Since we are going to talk about Copy-on-Write, is very important to understand the Swift value semantics.

What is this Copy-on-Write?

In Swift when you have large value type and have to assign or pass as a parameter to a function, copying it can be really expensive in terms of performance because you'll have to copy all the underlying data to another place in memory.

Trying to minimize the issue, the Swift Standard library implements this set of mechanisms for some value types such as Array, where the value will be copied only upon mutation, and even then, only if it has more than one reference to it, because if this value is uniquely referenced, it doesn’t need to copy, it can be just mutated on the reference. So, just assign to a variable or pass an Array to a function doesn’t necessarily means it’ll be copied and that really improve the performance.

Something really important to know is …

Copy-on-Write is not a default behavior of value types, is something that is implemented on the Swift Standard Library for certain types such as Array and Collections. So, it means that not every value type in the Standard Library has this behavior. Also, the value types that you create doesn’t have it, unless you implement it yourself. This is something I’ll talk about later in the next section.

Let's see in practice how works with an example:

import Foundation

func print(address o: UnsafeRawPointer ) {
    print(String(format: "%p", Int(bitPattern: o)))
}

var array1: [Int] = [0, 1, 2, 3]
var array2 = array1

//Print with just assign
print(address: array1) //0x600000078de0
print(address: array2) //0x600000078de0
//Let's mutate array2 to see what's
array2.append(4)

print(address: array2) //0x6000000aa100

//Output
//0x600000078de0 array1 address
//0x600000078de0 array2 address before mutation
//0x6000000aa100 array2 address after mutation

This is a simple way to show how Copy-on-Write works. Basically creating the array1 with values and assign to array2, where because of Copy-on-Write it isn’t being copied so both point to the same address. So the array2 data is being copied only when we mutate it as you can see on lines 15–17.