Copy on Write (CoW) is an interesting concept because it helps developers understand how Swift optimizes memory management by avoiding unnecessary data duplication. CoW ensures that objects are only copied when modified, saving resources and improving performance. This concept is particularly relevant when working with value types like structs or arrays, where multiple references can exist to the same data.

By providing an example—such as modifying a struct inside an array—we aim to demonstrate how CoW prevents redundant copying, enhancing both efficiency and memory usage in iOS apps.

CoW - Copy-on-Write

Copy on Write (CoW) is an optimization technique used in Swift for value types, particularly for collections like Arrays, Strings, and Dictionaries. It allows these value types to share the same underlying storage until a mutation occurs, improving performance and memory efficiency.

How Copy on Write Works

  1. Initial Assignment: When a value type is assigned to a new variable, Swift performs a shallow copy, creating a new reference to the same underlying data3.

  2. Shared Storage: Multiple instances of the value type share the same memory location, reducing unnecessary copying.

  3. Mutation Trigger: When one instance attempts to modify its contents, Swift creates a full copy of the data for that instance.

  4. Preserved Originals: The original instance remains unchanged, maintaining value semantics.

Benefits

  • Performance Optimization: Avoids unnecessary copying of large data structures.

  • Memory Efficiency: Reduces memory usage by sharing data until modification is required.

  • Value Semantics: Maintains the expected behavior of value types while improving efficiency.

Implementation

CoW is built into Swift’s standard library for Arrays, Strings, and Dictionaries. For custom value types, developers can implement CoW behavior using techniques like:

  1. Using isUniquelyReferenced to check if a copy is needed before mutation.

  2. Wrapping the value in a reference type (e.g., a class) and managing copies manually.

It’s important to note that CoW is not automatically available for custom value types and requires explicit implementation.

Example code this time is provided via Playground:

import UIKit

final class Wrapper {
    var data: [Int]
    
    init(data: [Int]) {
        self.data = data
    }
}

struct CoWExample {
    private var storage: Wrapper

    init(data: [Int]) {
        self.storage = Wrapper(data: data)
    }

    mutating func modifyData() {
        print("Memory @ before updating: \(Unmanaged.passUnretained(storage).toOpaque())")
        
        if !isKnownUniquelyReferenced(&storage) {
            print("Making a copy of the data before modifying it.")
            storage = Wrapper(data: storage.data) // Created a copy
        } else {
            print("Update without copy, unique reference.")
        }

        storage.data.append(4)  // Modify array from class inside
        print("@ Memory after updaing: \(Unmanaged.passUnretained(storage).toOpaque())")
    }

    func printData(_ prefix: String) {
        print("\(prefix) Data: \(storage.data) | Memory @: \(Unmanaged.passUnretained(storage).toOpaque())")
    }
}

// Use  Copy-on-Write
var obj1 = CoWExample(data: [1, 2, 3])
var obj2 = obj1  // Both instances share same memory @

print("Before updating obj2:")
obj1.printData("obj1:")
obj2.printData("obj2:")

print("\nUpdating obj2:")
obj2.modifyData() // Here will take place copy when there's a new reference

print("\nAfter updating obj1:")
obj1.printData("obj1:")
obj2.printData("obj2:")

Key Components:

  1. Wrapper Class:

    • final class that holds an array of integers (data).

    • It is used as the underlying storage for the CoWExample struct.

  2. CoWExample Struct:

    • Contains a private property storage of type Wrapper.

    • Implements the Copy-on-Write mechanism in the modifyData() method.

    • Provides a method printData(_:) to print the current data and memory address of the storage object.

  3. modifyData() Method:

    • Checks if the storage object is uniquely referenced using isKnownUniquelyReferenced(&storage).

    • If the storage object is not uniquely referenced (i.e., it is shared), a new copy of the Wrapper object is created before modifying the data.

    • If the storage object is uniquely referenced, the data is modified directly without creating a copy.

    • Appends the value 4 to the data array.

  4. Memory Address Tracking:

    • The memory address of the storage object is printed before and after modification to demonstrate whether a copy was made.

Run playground

Lets run the playground and analyze logs:

After obj2=obj1 assignation, both share exactly same memory @ddress. But when obj2 is being updated:

Is at that moment when obj2 is allocated in a different @dress space and then updated.

Conclusions

A quick answer is that the difference between Value Types and Reference Types lies in how they are assigned. When a Value Type is assigned, an isolated copy is created. However, in reality, after assignment, Value Types behave similarly to Reference Types. The key difference emerges when an update occurs—at that point, a new allocation is performed.

You can find source code used for writing this post in following repository

References

Copyright © 2024-2025 JaviOS. All rights reserved