What exactly does isKnownUniquelyReferenced check, and how do you defeat it by accident?
Predict first
What does this print?
final class Storage {}
var storage = Storage()
var cache = [Storage]()
cache.append(storage)
print(isKnownUniquelyReferenced(&storage))
- true
- false
Mental model. The check asks how many hands are on the rope. A cached copy or a closure is an extra hand, and the answer is never “just me” again.
isKnownUniquelyReferenced(&ref) returns true when the object’s strong count is exactly one. Weak and unowned references are ignored. It takes the variable inout so it can read the reference in place without creating a temporary copy that would add a count. For objects it cannot inspect (a non-native Objective-C object) it conservatively returns false. It is a check, not a lock: if two threads share the same variable, the test and the mutation are not atomic, so uniqueness assumes one writer.
Almost every “my copy-on-write type keeps copying” bug is a stray strong reference: the storage was also appended to a cache, captured by a closure, stored in a second property, or read into a local let before the check. Each adds a count, so the check says “shared” and every write copies the whole buffer, turning an O(1) append into O(n). Find it by looking for who else holds the storage, in the Memory Graph or by logging when the clone branch runs.
Putting the storage in the array added a second strong reference, so it is no longer unique. Every later write would copy it.
Common traps
- Sharing the storage with a cache “for speed”. It defeats uniqueness, so every write clones.
- Assuming the check makes mutation thread-safe. It only tells you about counts at one instant.
They might follow up with
- How does
Arraycheck this? Through the same idea on its buffer object, with an internal fast path. - What about a weak reference to the storage? It does not break uniqueness, so a weak back-pointer is safe.
Related questions
- 3.5.1 How do you implement copy-on-write for your own struct?
- 3.5.4 Why does a mutation sometimes copy the whole array, and how do you prove it?
- 3.1.2 What is a reference count, and how do you check that you hold the only reference?
← How do you implement copy-on-write for your own struct? What is an Array in memory, and what is special about the empty array? →