If you have a @property
for an object who’s name starts with NSMutable
, and you use the copy
declaration attribute, then the code that is synthesized for you is not correct. Because it uses copy
, not mutableCopy
, to do the copy during assignment, the value will not be mutable.
Here’s a demonstration,
@interface Gotcha : NSObject { NSMutableString *mutableString; } @property (copy) NSMutableString *mutableString; @end @implementation Gotcha @synthesize mutableString; @end
... Gotcha *x = [[[Gotcha alloc] init] autorelease]; x.mutableString = [NSMutableString stringWithString:@"I am mutable."]; [x.mutableString appendString:@" Look at me change."];
It crashes with the message,
*** Terminating app due to uncaught exception ‘NSInvalidArgumentException’, reason: ‘Attempt to mutate immutable object with appendString:’
copy
vs mutableCopy
The copy
method returns an immutable copy of an object, even if the object is mutable. Eg the copy
of an NSMutableString
is an immutable NSString
.
The mutableCopy
method returns a mutable copy of an object, even if the object is not mutable. Eg., the mutableCopy
of an NSString
is an NSMutableString
object.
@synthesize
always uses copy
in the setters it generates. That means an immutable copy will always be stored, even if an NSMutable
object is given.
Workarounds
Apple says, ” …In this situation, you have to provide your own implementation of the setter method..” (To me, this isn’t as satisfying of an answer as a mutablecopy
attribute. But it’s what Apple has chosen, so all we can do is file enhancement requests and trust their reasoning).
Another workaround is to make any property that is a mutable value readonly
. If something is mutable, you can change it’s contents directly, so there is rarely a need to set it. For example, with the Gotcha
object, it’s just as easy to say
[x.mutableString setString:@"New String"];
as
x.mutableString = [NSMutableString stringWithString:@"New String"];
Finally, you might consider using an immutable readwrite
property. In my experience, immutable objects are generally safer, and often faster (I owe you an article on this, but there are plenty of articles on the benefits of immutability). I know this sounds like a smart-ass suggestion, and it won’t be a good answer much of the time. But it’s surprising how often things turn out not to need mutability once a design matures. Since reducing the mutability of an object often improves it, and it does work around the problem, it’s worth at least considering this solution.
Why not Just Use retain
?
Changing the copy
to retain
well let you assign a mutable object, but I do not like the idea. My article, Prefer copy
Over retain
explains in detail. My biggest worry is aliasing,
NSMutableString *aLocalString = [NSMutableString string]; x.mutableString = aLocalString; [x doSomething]; //aLocalString might have been changed by doSomething! // // 300 lines later... // [aLocalString appendString:@" now with more bugs!"]; //x has been changed too!
Using retain
with NSMutable
objects is asking for bugs.
Thanks for the tips you saved me hours of frustration.
Comment by Bob Donovan — January 29, 2010 @ 11:31 am