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
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.
Shared Storage: Multiple instances of the value type share the same memory location, reducing unnecessary copying.
Mutation Trigger: When one instance attempts to modify its contents, Swift creates a full copy of the data for that instance.
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:
Using
isUniquelyReferenced
to check if a copy is needed before mutation.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:
Wrapper
Class:A
final
class that holds an array of integers (data
).It is used as the underlying storage for the
CoWExample
struct.
CoWExample
Struct:Contains a private property
storage
of typeWrapper
.Implements the Copy-on-Write mechanism in the
modifyData()
method.Provides a method
printData(_:)
to print the current data and memory address of thestorage
object.
modifyData()
Method:Checks if the
storage
object is uniquely referenced usingisKnownUniquelyReferenced(&storage)
.If the
storage
object is not uniquely referenced (i.e., it is shared), a new copy of theWrapper
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 thedata
array.
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
- Swift - Managed Buffers
Apple Developer Documentation