Copy-on-write in Swift

Subscribe to my newsletter and never miss my upcoming articles

Hello ladies and gentlemen, Leo here.

Today we'll explore an amazing language feature called copy-on-write. First of all we will introduce the differences between value types and reference types, after that I'll show how to check the memory address and see the mechanism of copy on write working. No more talking, let's dive in.

Image explanation: copyst in art are a very old tradition that continues until today, since today subject is copying feature, I thought it would be a good reference :)

Value types Vs Reference types

As the Apple's Swift blog explain we can define like this:

Types in Swift fall into one of two categories: first, “value types”, where each instance keeps a unique copy of its data, usually defined as a struct, enum, or tuple. The second, “reference types”, where instances share a single copy of the data, and the type is usually defined as a class.

And we can see this particular behavior when copying variables. See below:

Value type example:

func address(o: UnsafeRawPointer) -> Int {
    return Int(bitPattern: o)
}

struct Car { var name: Int = -1 }
var a = Car()
var b = a                        // a is copied to b
a.name = 42                        // Changes a, not b
print("\(a.name), \(b.name)")    // prints "42, -1"
print(NSString(format: "%p", address(o: &a))) // -> 0x109299fc0
print(NSString(format: "%p", address(o: &b))) // -> 0x109299fc8

As you can see, and test yourself in playground, when we copy a struct we don't change the original one. This way we literally copy the values to other variable and the memory address is different too as soon it's copied.

Screen Shot 2020-11-19 at 08.29.13.png

But if Car were a class the result would be very different, let's see:

func addressHeap<T: AnyObject>(o: T) -> Int {
    return unsafeBitCast(o, to: Int.self)
}

class Car { var name: Int = -1 }
var a = Car()
var b = a                        // a is copied to b
a.name = 20                        // Changes a and b
print("\(a.name), \(b.name)")    // prints "20, 20"

print(NSString(format: "%p", addressHeap(o: a))) // -> 0x600000efa2e0
print(NSString(format: "%p", addressHeap(o: b))) // -> 0x600000efa2e0

As you can see the memory address are the same, the values of the two variables are the same because they share the same reference in memory.

Screen Shot 2020-11-19 at 08.28.12.png

Now that we're already comfortable with value and reference types we can go further in our study.

Copy on Write

As the wiki explain to us:

Copy-on-write (COW), sometimes referred to as implicit sharing[1] or shadowing,[2] is a resource-management technique used in computer programming to efficiently implement a "duplicate" or "copy" operation on modifiable resources.[3] If a resource is duplicated but not modified, it is not necessary to create a new resource; the resource can be shared between the copy and the original. Modifications must still create a copy, hence the technique: the copy operation is deferred until the first write. By sharing resources in this way, it is possible to significantly reduce the resource consumption of unmodified copies, while adding a small overhead to resource-modifying operations.

In Swift, Array, Dictionary and so on are all value types. And they implement that feature mentioned above. We only have a new instance in memory of them when we modify the buffer. This is a very important memory optimization, because those types tends to get bigger and bigger since they aggregate data together. Under the hood they are all buffer implemented to get the maximum memory efficiency. See example below:

func address(o: UnsafeRawPointer) -> Int {
    return Int(bitPattern: o)
}

struct Car { var name: Int = -1 }
var a = [Car()]
var b = a

print(NSString(format: "%p", address(o: &a))) // -> 0x6000035289b0 are the same
print(NSString(format: "%p", address(o: &b))) // -> 0x6000035289b0 are the same

a.append(Car()) // now modifying the first one, now Swift should generate the new copy

print(NSString(format: "%p", address(o: &a))) // -> 0x600003511c10 copy on write feature
print(NSString(format: "%p", address(o: &b))) // -> 0x6000035289b0 b still the same

Now we see how Swift implement the copy on write. As you can check above when we COPY the array to another variable, it only copy the reference.

But wait array isn't a value type? Yes it is! But thanks to the copy on write feature, only when we WRITE to the array that Swift copy that new value to a new memory address. This enables a significantly reduce the on resource consumption and when we talk about iPhones the resources aka battery/networking/capacity are limited.

Although array is a value type, therefore it should always copy value, if you don't modify the data it won't copy and this is beauty of this, you can copy and share arrays, dictionaries instances around without having to think about memory perfomance.

Please let me know if you have any thoughts or feedbacks, that's very important to me.

Thank you for the reading and... That's all folks.

credit: image

No Comments Yet