23

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?

P.J.Radadiya
  • 1,493
  • 1
  • 12
  • 21
Akshay Rangnekar
  • 272
  • 2
  • 11
  • 1
    `@property (copy, nonatomic) NSMutableArray *words;` no use of copy, you shuld use retain. – Anoop Vaidya Feb 13 '13 at 15:18
  • or strong instead of retain – The dude Feb 13 '13 at 15:20
  • 1
    possible duplicate of [Using -mutableCopyWithZone: on custom class makes it immutable](http://stackoverflow.com/questions/14841130/using-mutablecopywithzone-on-custom-class-makes-it-immutable) –  Feb 13 '13 at 15:21
  • Ha, I came here because I was hoping today there was a way of doing `@property (nonatomic, mutableCopy)`. Oh well, guess it'll never happen now with the imminent phasing out of Objective-C in favor of swift. – dave Dec 09 '14 at 17:53

2 Answers2

37

-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).

CodaFi
  • 43,043
  • 8
  • 107
  • 153
  • 5
    To be clear: the `copy` happens in the synthesized implementation of the setter, not at the call site. – bbum Feb 13 '13 at 16:59
  • 1
    @bbum Of course. This was just for brevity's sake. I really didn't want to get into what properties actually were for this kind of question :) – CodaFi Feb 13 '13 at 17:47
  • @CodaFi but NSArray is adopted the NSMutableCopying protocol so why it return NSArray instead of NSMutableArray?? – Mahmoud Adam Sep 24 '14 at 14:01
  • NSMutableCopying merely says that a class responds to `-mutableCopy(WithZone:)`, not that it returns mutable copies of itself *in all cases*. Protocols in Objective-C have little to do with behavior and implementation and everything to do with response to particular messages. – CodaFi Sep 24 '14 at 14:54
  • @bbum, I think that tthe language clearly misses a keyword 'mutableCopy' for properties to make it possible to specify that one wants the setter to call 'mutableCopy' instead of 'copy'. – Kaiserludi Nov 11 '15 at 16:13
  • 1
    @Kaiserludi We discussed it at great lengths when adding `@property` to the language. Ultimately, `mutableCopy` proved to be enough of an edge case with enough one-off business logic related to said one-offs that it wasn't deemed worthy of addition. – bbum Nov 12 '15 at 16:29
8

Properties aren't magical, they're just shorthand. Declaring a @property on your object tells the compiler to create a backing instance variable and accessor methods for it. The actual generated code depends on the attributes you set on your property.

It's important to remember that setting a property using dot syntax is also shorthand. When you call…

self.words = mutWords;

…you're actually invoking the generated accessor method behind the scenes, like this:

[self setWords:mutWords];

Since you specified the copy attribute on your property, you've told the compiler to generate that -setWords: accessor method with code that looks something like this:

- (void)setWords:(NSMutableArray *)words
{
    _words = [words copy];
}

Knowing all that, you can see what's happening: the generated setter method will call -copy on the input argument, and assign the result to the backing instance variable. Because the -copy method is always implemented to return a non-mutable object (performing [aMutableString copy] will return an NSString, and so on) setting that property will always store a non-mutable copy.

macserv
  • 3,546
  • 1
  • 26
  • 36