Why does a (copy, nonatomic) NSMutableArray property create NSArrays?

Akshay Rangnekar picture Akshay Rangnekar · Feb 13, 2013 · Viewed 9.5k times · Source

I made a mistake while creating a TableView class, and accidentally kept my @property as copy when I defined it:

@property (copy, nonatomic) NSMutableArray *words; 

I initialised the array "correctly": (note this is the third attempt, so please ignore the fact that I'm not using mutableCopy and other better ways of doing this)

NSArray *fixedWords = @[@"Eeny", @"Meeny", @"Miny", @"Moe", @"Catch", @"A", @"Tiger", @"By", @"His", @"Toe"];
NSMutableArray *mutWords = [[NSMutableArray alloc] initWithArray:fixedWords];
self.words = mutWords;

However when I later came to reorder the array, it crashed on the removeObjectAtIndex line:

id object = [self.words objectAtIndex:fromIndexPath.row];
NSUInteger from = fromIndexPath.row;
NSUInteger to = toIndexPath.row;
[self.words removeObjectAtIndex:from];

With the error message

unrecognized selector sent to instance

Took a lot of digging to figure out that this is because the copy means that assigning the NSMutableArray results in creation of a standard (nonmutable) NSArray. Can anyone explain why this is the correct behaviour?

Answer

CodaFi picture CodaFi · Feb 13, 2013

-copy, as implemented by mutable Cocoa classes, always returns their immutable counterparts. Thus, when an NSMutableArray is sent -copy, it returns an NSArray containing the same objects.

Because words has the memory qualifier copy, this line:

NSMutableArray *mutWords = [[NSMutableArray alloc] initWithArray:fixedWords];
self.words = mutWords;

Expands out to:

NSMutableArray *mutWords = [[NSMutableArray alloc] initWithArray:fixedWords];
self.words = [mutWords copy];

Given that NSMutableArray is a subclass of NSArray, the compiler doesn't complain, and you now have a ticking time bomb on your hands because NSArray does not recognize it's mutable subclass' methods (because it cannot mutate it's contents).