Copy on Write in Swift explained with examples

Photo by Zan / Unsplash

Theory

According to Wikipedia - Copy on Write "is a resource-management technique used in computer programming to efficiently implement a "duplicate" or "copy" operation on modifiable resources."

As confusing as it may seem, the mechanism behind is very simple - we want to make a copy of the resource (class, struct, etc) only when it is mutated. If we just use resources as read-only, it stays in one place.

Swift Copy on Write

Swift has two basic data types - reference type (class, closure) and value type (struct, enum, touple).

Reference type points to the same place in memory. Every time you assign a reference type to a new value it implicitly points to the shared object.

Value type is copied to the new place every time you assign, initialize, and pass it as an argument.

This is true for almost all of the swift structs. But some of them are special. For example Arrays and other Collections. They implement copy on write mechanism. This means the array will be copied only when we mutate its content (not always - I'll show you an exception!).

Coding example

We will create a new command line tool for testing our assumptions. We will need something to print memory addresses, dummy structures and classes, and testing code.

Helpers

Our helpers will print addresses to the console:

// Prints address of structure
func address(o: UnsafeRawPointer)  {
    
    print(NSString(format: "%p", Int(bitPattern: o)))
}

// Prints address of class
func addressHeap<T: AnyObject>(o: T) {
    
    print(NSString(format: "%p", unsafeBitCast(o, to: Int.self)))
}

Dummy types

We will need one dummy class and one dummy struct for demonstration:

struct Foo {
    var foo = 1
}
class Bar {
    var bar = 1
}

Testing

We are ready to test swift memory management! I'll start with a basic test, is class really reference type and struct value type?

All right! We can see by addresses, that struct Foo is copied to a new place in memory, but class Bar is referenced and pointed to shared space! Also, classes and structs are far apart in memory, since they are stored differently (stack/heap). For our custom struct, there is no Copy on Write mechanism. We have to check what will happen for the swift array!

First I'll test an array of structs:

We can see, before mutation, two arrays point to the same place in memory - 0x600001700720. After mutation, one of them is copied to the new place 0x600001700760. Copy on write works!

But will it always work for an array? Maybe we can "break" it a bit. Let's test it with an array of classes!

Now that doesn't seems right! We mutate barArray1 content but it is not copied! Why?! The answer is simple, since the class is a reference type, we don't mutate its reference, we change its property inside the shared instance. There is no change of address  barArray1[0] so there is no copying of the array. This will always happen when we store the reference type inside the value type.

Can I make my struct to be CoW?

Of course! If you feel it is important (the struct is heavy to copy and most of the time it is used without mutating, why not!), Apple gives us tips on how to do that on GitHub:

final class Ref<T> {
  var val: T
  init(_ v: T) { val = v }
}

struct Box<T> {
  var ref: Ref<T>
  init(_ x: T) { ref = Ref(x) }

  var value: T {
    get { return ref.val }
    set {
      if !isKnownUniquelyReferenced(&ref) {
        ref = Ref(newValue)
        return
      }
      ref.val = newValue
    }
  }
}

We can also leverage new swift features to elevate the solution to be more readable by using a simple property wrapper:

@propertyWrapper
public struct CopiedOnWrite<T: Any> {
    public let wrapped: Box<T>
    
    public var wrappedValue: T {
        return wrapped.value
    }
    
    public init(wrappedValue: T) {
        wrapped = Box(wrappedValue)
    }
}

With a neat trick, we don't have to think when using our Copy on Write mechanism - the property wrapper will allow us to use any value as it is plain var.

Artur Gruchała

Artur Gruchała

I started learning iOS development when Swift was introduced. Since then I've tried Xamarin, Flutter, and React Native. Nothing is better than native code:)
Poland