Copy Semantics

Understand shallow vs. deep copying, the copy and mutableCopy methods, the NSCopying/NSMutableCopying protocols, and why the copy property attribute is the correct defensive choice for NSString/NSArray/NSDictionary-typed properties.

You've used the copy attribute on properties throughout this course, probably without dwelling on exactly why. This lesson steps back and covers copy semantics as their own topic: what a copy actually produces, how shallow copying differs from deep copying, and — most importantly — why copy is the idiomatic, defensive choice for properties typed as NSString, NSArray, or NSDictionary. This is a preview and overview; the sibling addendum lesson 'NSCopying and NSMutableCopying' goes deeper into implementing the protocol yourself for custom classes.

copy and mutableCopy: the two entry points

Foundation objects that support copying respond to two methods: -copy, which returns an immutable copy of the object, and -mutableCopy, which returns a mutable copy. Both are declared through two protocols — NSCopying (for -copyWithZone:, which backs -copy) and NSMutableCopying (for -mutableCopyWithZone:, which backs -mutableCopy). Most of Foundation's core value types — NSString, NSArray, NSDictionary, NSSet, and their mutable counterparts — conform to both.

Calling -copy on an NSMutableString gives you back an immutable NSString snapshot of its current contents; calling -mutableCopy on an NSArray gives you back a new NSMutableArray you can freely modify without affecting the original.

NSCopying and NSMutableCopying: what conformance means

NSCopying declares exactly one required method: -copyWithZone:, which the class implements to describe how to produce an independent copy of itself. NSMutableCopying similarly declares -mutableCopyWithZone:. When you call the convenience method -copy on any object, Foundation actually invokes -copyWithZone: under the hood (the 'zone' parameter is a historical memory-zone concept that's essentially vestigial on modern Objective-C runtimes, but the method name and protocol remain).

A class only supports -copy if it conforms to NSCopying and implements -copyWithZone:; calling -copy on an object that doesn't conform will crash with 'unrecognized selector.' All the standard immutable/mutable value classes you use daily already conform, so you rarely think about this until you write your own custom class — which is exactly what the sibling 'NSCopying and NSMutableCopying' lesson covers in depth.

Shallow copy vs. deep copy

This is the single most important distinction in this lesson. A shallow copy duplicates the container itself — a new NSArray, a new NSDictionary — but the elements or values inside it are not copied; the new container simply holds references to the exact same objects as the original. A deep copy goes further, recursively copying the contained objects too, so that the new structure shares no objects at all with the original, at any level.

Foundation's built-in -copy on collection classes (NSArray, NSDictionary, NSSet) performs a shallow copy by default. If you have an NSArray of NSMutableString instances and you call -copy on the array, you get a new, independent array object — but it still points at the very same mutable string instances as the original array. Mutate one of those strings through either array's reference, and you'll see the change reflected no matter which array you access it through, because they share the same underlying objects.

Deep copying is not automatic; if you need it, you generally have to implement it yourself — either by writing a custom -copyWithZone: for your own container class that explicitly copies each contained object as well, or by manually walking a Foundation collection and copying each element before building the new collection.

Why copy is the correct property attribute for NSString/NSArray/NSDictionary

This is the practical payoff of understanding copy semantics. Consider a property declared as @property (nonatomic, copy) NSString *name;. Why copy instead of strong? The answer is defensive programming against mutable subclasses.

NSString is the public interface of a class cluster, and NSMutableString is a subclass of it. That means any caller can legally pass an NSMutableString wherever an NSString parameter or property is expected — the type system allows it, because NSMutableString is-a NSString. If your property were declared strong instead of copy, you would simply be storing a reference to whatever object the caller handed you. If that object happens to be an NSMutableString the caller still holds a reference to elsewhere, they can mutate it after assignment — silently changing your property's value out from under you, without ever going through your setter again.

Declaring the property copy closes this hole. copy invokes -copy at assignment time, which for a mutable string produces an immutable NSString snapshot of its contents at that exact moment. Even if the caller later mutates their own NSMutableString instance, your property is unaffected — you're holding an independent, immutable copy, not a shared reference to their mutable object. This is one of the most important defensive-property idioms in Objective-C, and it's why copy is the conventional, correct choice for any property typed as NSString, NSArray, or NSDictionary (and their common variants like NSSet), even when you never expect callers to actually pass a mutable subclass — the property attribute protects you regardless of caller intent.

copy vs. strong: a quick comparison

It's worth being explicit about the mechanical difference. A strong property retains whatever object reference is assigned to it; if that reference is later mutated by anyone holding it, your property observes the mutation too, because there's only ever one object. A copy property invokes -copy at assignment time and retains the result; the object your property refers to is a distinct, independent instance from whatever was passed in, so external mutation of the original cannot affect your property afterward.

This is precisely why copy is recommended specifically for immutable value-like types where a mutable subclass exists (NSString/NSMutableString, NSArray/NSMutableArray, NSDictionary/NSMutableDictionary), and generally not needed for types without a meaningful mutable counterpart being passed around.

• strong: retains the exact object passed in; external mutation of that object is visible through your property. • copy: retains an independent copy made at assignment time; external mutation of the original object afterward does not affect your property. • copy is the conventional choice for NSString, NSArray, NSDictionary (and NSSet) typed properties specifically because mutable subclasses of each exist and can legally be passed in.

Preview: the deeper NSCopying and NSMutableCopying lesson

This lesson deliberately stays at the level of using copy/mutableCopy and understanding why the copy property attribute matters. Implementing -copyWithZone: and -mutableCopyWithZone: yourself for a custom class — including how to correctly handle deep copying of nested mutable objects, how to call through to super when subclassing a copyable class, and common pitfalls (like forgetting to copy mutable ivars, producing a copy that still shares state) — is covered in full in the sibling addendum lesson, 'NSCopying and NSMutableCopying.' Treat this lesson as the conceptual foundation and that one as the hands-on implementation guide.

The idiomatic copy property

copy is preferred over strong here specifically because NSMutableString (a subclass of NSString) could otherwise be assigned and later mutated out from under you. The same reasoning applies to NSArray, NSDictionary, and NSSet-typed properties given their respective mutable subclasses. For custom classes, copy only works if the class conforms to NSCopying and implements -copyWithZone:.

@property (nonatomic, copy) NSString *name;

Code Examples

Because name is declared copy, assigning mutableName to p.name triggers a -copy call, producing an independent immutable NSString snapshot at that moment. The subsequent mutation of mutableName has no effect on p.name, because they no longer share the same underlying object. Expected result: Alice p.name = mutableName;

With strong, the property simply retains whatever reference was assigned — in this case, the very same NSMutableString instance the caller still holds. When the caller mutates it afterward, the property's observed value changes too, even though no one ever wrote p.name = something again. This is exactly the bug copy is designed to prevent.

Common mistake: Declaring an NSString-typed property as strong instead of copy allows a caller-held NSMutableString to be mutated after assignment, silently changing the property's value without going through the setter.

Calling -copy on original produces a new, independent NSArray object (shallow is not the same array instance as original), but the single element inside it is not copied -- both arrays hold a reference to the exact same NSMutableString instance. Mutating that shared string is visible through either array. This is the defining trait of a shallow copy: the container is new, the contents are shared.

Because Foundation's built-in -copy on NSArray is only shallow, achieving a true deep copy requires manually copying each element as you build the new collection. Here, each mutable string is individually mutableCopy'd into the new array, so deepCopy shares no objects at all with original -- mutating one has no effect on the other.

[deepCopy addObject:[s mutableCopy]]; // copy each element too

@interface Person : NSObject
@property (nonatomic, copy) NSString *name;
@end

@implementation Person
@end

NSMutableString *mutableName = [NSMutableString stringWithString:@"Alice"];
Person *p = [[Person alloc] init];
p.name = mutableName;

[mutableName appendString:@" (hacked)"];

NSLog(@"%@", p.name);
// Output: Alice
// (NOT "Alice (hacked)") -- p.name holds an independent copy,
// unaffected by the later mutation of mutableName.
@interface UnsafePerson : NSObject
@property (nonatomic, strong) NSString *name; // BUG: should be copy
@end

@implementation UnsafePerson
@end

NSMutableString *mutableName = [NSMutableString stringWithString:@"Alice"];
UnsafePerson *p = [[UnsafePerson alloc] init];
p.name = mutableName;

[mutableName appendString:@" (hacked)"];

NSLog(@"%@", p.name);
// Output: Alice (hacked)
// The property silently changed without ever going through the setter again.
NSMutableString *item = [NSMutableString stringWithString:@"apple"];
NSArray *original = @[item];
NSArray *shallow = [original copy];

[item appendString:@" pie"];

NSLog(@"%@", original.firstObject);
// Output: apple pie
NSLog(@"%@", shallow.firstObject);
// Output: apple pie -- same underlying object, both arrays see the mutation
NSArray<NSMutableString *> *original = @[
    [NSMutableString stringWithString:@"apple"],
    [NSMutableString stringWithString:@"banana"]
];

NSMutableArray *deepCopy = [NSMutableArray arrayWithCapacity:original.count];
for (NSMutableString *s in original) {
    [deepCopy addObject:[s mutableCopy]]; // copy each element too
}

[original.firstObject appendString:@" pie"];

NSLog(@"%@", original.firstObject);
// Output: apple pie
NSLog(@"%@", deepCopy.firstObject);
// Output: apple -- unaffected, because deepCopy holds its own independent strings

Where this shows up in real codebases

This exact bug -- a strong NSString/NSArray/NSDictionary property that should have been copy -- is one of the most common sources of 'spooky action at a distance' in Objective-C codebases: a value changes somewhere seemingly unrelated to any code that reassigns the property, because the underlying mutable object was shared and mutated elsewhere. It's subtle enough that it frequently survives code review and only surfaces as an intermittent bug much later. This is exactly why copy is treated as the default, idiomatic choice for these property types across virtually every Objective-C style guide and why linters and static analyzers often flag a strong NSString/NSArray/NSDictionary property as worth double-checking.

@property (nonatomic, strong) NSString *name;

@property (nonatomic, copy) NSString *name;

strong simply retains whatever object is assigned, including a caller's NSMutableString instance. If the caller mutates that instance after assignment, your property's value silently changes too, without your setter ever running again. copy defends against this by snapshotting an independent, immutable copy at assignment time -- the standard idiom for NSString, NSArray, and NSDictionary properties.

What does calling -copy on an NSArray of NSMutableString elements actually produce? A. A completely independent array where every string is also independently copied (a deep copy) B. A new, independent array object, but one that still references the exact same string objects as the original (a shallow copy) C. The exact same array object, with retain count incremented D. A crash, because NSArray doesn't conform to NSCopying

B. A new, independent array object, but one that still references the exact same string objects as the original (a shallow copy) — Foundation's built-in -copy on collection classes performs a shallow copy by default: the container is new and independent, but the elements inside are shared references to the same objects, not independently copied.

Why is copy the recommended property attribute for an NSString-typed property? A. Because copy is faster than strong for all object types B. Because it defends against a caller passing a mutable NSMutableString instance and mutating it after assignment, which strong would not protect against C. Because NSString cannot be retained with strong D. Because copy is required by ARC for all object properties

B. Because it defends against a caller passing a mutable NSMutableString instance and mutating it after assignment, which strong would not protect against — NSMutableString is a subclass of NSString and can legally be assigned to an NSString-typed property. copy snapshots an independent, immutable copy at assignment time, so later mutation of the caller's original object cannot silently change your property's value -- something a strong property would not protect against.

Which protocol method does calling -copy on an object actually invoke under the hood? A. -mutableCopyWithZone: B. -copyWithZone: C. -duplicate D. -clone

B. -copyWithZone: — The convenience method -copy is implemented in terms of -copyWithZone:, the single required method of the NSCopying protocol that the class conforms to.

What is the difference between a shallow copy and a deep copy? A. A shallow copy duplicates the container only, sharing references to the same contained objects; a deep copy recursively copies the contained objects too B. A shallow copy is faster but always incorrect; a deep copy is the only valid form of copying C. A shallow copy applies only to NSString; a deep copy applies only to NSArray D. There is no practical difference in Objective-C

A. A shallow copy duplicates the container only, sharing references to the same contained objects; a deep copy recursively copies the contained objects too — Shallow copy: new container, shared element references. Deep copy: new container AND independently copied contained objects, recursively as needed. Foundation's built-in collection -copy is shallow by default.

Where should you look to learn how to implement -copyWithZone: yourself for a custom class, including deep-copying nested mutable objects? A. This lesson covers it in full depth already B. The sibling addendum lesson 'NSCopying and NSMutableCopying', which this lesson previews but does not fully implement C. The 'Class Clusters' lesson D. It cannot be done in Objective-C

B. The sibling addendum lesson 'NSCopying and NSMutableCopying', which this lesson previews but does not fully implement — This lesson is explicitly a preview/overview of copy semantics; the sibling 'NSCopying and NSMutableCopying' addendum lesson goes deeper into implementing the protocol yourself, including handling deep copies of nested mutable objects.

You're writing a Playlist class with a property that holds an ordered list of song titles, typed as NSArray<NSString *> *songTitles. A caller might pass in an NSMutableArray. Declare the property correctly, and explain in one sentence why your choice protects Playlist from being mutated by the caller after assignment.

@property (nonatomic, copy) NSArray<NSString *> *songTitles; // copy invokes -copy on whatever is assigned, producing an independent NSArray snapshot at that moment; even if the caller passed an NSMutableArray and later mutates it, Playlist's songTitles remains unaffected because it no longer shares the same underlying array object.

Think about what NSMutableArray is a subclass of, and what copy does at assignment time versus what strong does.