How does NSCopying work, and why use a copy property for strings and blocks?

4.3.3 · L2 · Interview

Predict first

What does this print?

let a = NSArray(array: [1])
print((a.copy() as AnyObject) === a)
  • true
  • false

Predict first

What does this print?

let s = NSMutableString(string: "a")
let snapshot = s.copy() as! NSString
s.append("b")
print(snapshot, s)
  • ab ab
  • a ab
  • a a

Mental model. Copying a laminated card just hands you the same card. Copying a whiteboard photographs what it says now, so later scribbles do not change your picture.

NSCopying gives copy, NSMutableCopying gives mutableCopy. For an immutable object a conforming class may return itself, retained, from copy: copying NSArray or NSString is free. A mutable object’s copy returns a new immutable snapshot, and its mutableCopy a new mutable one. Both are shallow for collections: the elements are shared, not duplicated.

That is why Objective-C properties holding NSString, NSArray, NSDictionary or blocks use the copy attribute: someone may pass an NSMutableString, and storing it with strong lets them mutate your state behind your back. copy takes a snapshot (or returns the same immutable instance for free). Blocks need it for a different reason: a block starts on the stack, and copy moves it to the heap so it survives. In Swift the equivalent is the @NSCopying attribute, and value types make most of this unnecessary.

An immutable object has nothing to protect, so copy just returns the same instance, retained.

copy on a mutable string returns an immutable snapshot of its current contents. Later changes affect only the original.

Common traps

  • Declaring a strong NSString property. A mutable string passed in can change under you.
  • Expecting copy on an array to duplicate its elements. It is shallow: each element is still the same object.

They might follow up with

  • How do you implement NSCopying? Return a new instance with the same state in copy(with:), or return self for an immutable class.
  • What does a deep copy need? Copying each element too, usually with archiving or a hand-written method.

Related questions

  • 2.9.4 Which Foundation types are value types in Swift, which are classes, and what happens to mutable ObjC types?
  • 4.7.2 How do Objective-C blocks differ from Swift closures, especially for capture?

← What is a class cluster, and what does it change for subclassing and…