Crash Caused by Mutating a Collection During Enumeration and the Mechanics of copy vs mutableCopy in Objective‑C
This article documents a runtime crash triggered by mutating an NSMutableSet while enumerating it, explains how the Objective‑C copy attribute works, demonstrates shallow and deep copy behavior for strings and collections, and provides low‑level source analysis of the copy implementation in the Apple runtime.
The author encountered a crash in an iOS project where an NSMutableSet was mutated during enumeration, resulting in the exception "Collection <__NSSetM> was mutated while being enumerated." The investigation began by reproducing the issue with a simple loop that removes items from the set while iterating.
NSMutableSet *mutableSet = [NSMutableSet setWithObjects:@"1",@"2",@"3", nil];
for (NSString *item in mutableSet) {
if ([item integerValue] < 3) {
[mutableSet removeObject:item];
}
}To solve the problem, the author recommends copying the collection before iterating, using the copy attribute or method to create an immutable snapshot.
The article then dives into the semantics of the copy keyword in Objective‑C, explaining that it is a property attribute that triggers a call to copyWithZone: on objects conforming to NSCopying . Implementing NSCopying requires defining the method:
- (id)copyWithZone:(NSZone *)zone;A concrete example is provided with a Person class that implements NSCopying , showing both shallow and deep copy implementations.
#import
NS_ASSUME_NONNULL_BEGIN
@interface Person : NSObject
- (instancetype)initWithName:(NSString *)name;
@property (nonatomic, copy) NSString *name;
@end
NS_ASSUME_NONNULL_ENDThe implementation demonstrates creating a new instance in copyWithZone: and a custom deepCopy that also copies the internal mutable set of friends.
- (id)copyWithZone:(NSZone *)zone {
Person *copy = [[Person allocWithZone:zone] initWithName:_name];
return copy;
}
- (id)deepCopy {
Person *copy = [[[self class] alloc] initWithName:_name];
copy->_persons = [[NSMutableSet alloc] initWithSet:_friends copyItems:YES];
return copy;
}The article further explores how the Objective‑C runtime implements the copy attribute. The generated getter simply returns the ivar, while the setter uses objc_setProperty , which eventually calls reallySetProperty . Depending on the shouldCopy flag, the setter either performs [newValue copy] , [newValue mutableCopy] , or a simple retain.
void objc_setProperty(id self, SEL _cmd, ptrdiff_t offset, id newValue, BOOL atomic, signed char shouldCopy) {
objc_setProperty_non_gc(self, _cmd, offset, newValue, atomic, shouldCopy);
}
static void reallySetProperty(id self, SEL _cmd, id newValue, ptrdiff_t offset, bool atomic, bool copy, bool mutableCopy) {
if (copy) {
newValue = [newValue copyWithZone:NULL];
} else if (mutableCopy) {
newValue = [newValue mutableCopyWithZone:NULL];
} else {
newValue = objc_retain(newValue);
}
// ... store newValue and release oldValue ...
}Practical experiments with NSString , NSMutableString , NSSet , and NSMutableSet illustrate the difference between shallow and deep copies. For immutable objects like NSString , copy returns the same instance (shallow copy), while mutableCopy creates a new mutable object. For mutable collections, copy produces an immutable snapshot (shallow copy of the container, elements are shared), whereas mutableCopy creates a new mutable container with the same element references (single‑level deep copy).
Key takeaways include:
Never use copy on properties of mutable collection types ( NSMutableArray , NSMutableSet , NSMutableDictionary ) because the resulting property will hold an immutable object.
When copying collections, the elements themselves are not duplicated; only the container is copied.
Understanding the runtime implementation helps diagnose crashes related to concurrent mutation during enumeration.
References to Apple documentation and source code are provided for further reading.
Sohu Tech Products
A knowledge-sharing platform for Sohu's technology products. As a leading Chinese internet brand with media, video, search, and gaming services and over 700 million users, Sohu continuously drives tech innovation and practice. We’ll share practical insights and tech news here.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.