0

I just put up a wrong answer (deleted)

The code was in response to this question. The OP wanted to know why they got a Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'Attempt to mutate immutable object with setString:

(My answer was a calamity of me not paying attention to what I was doing, being tired and still learning. The usual excuses. :-) )

When they tried to set the mutable string with:

 [firstName setString:@""];

The property is a NSMutableString

 @property (copy,nonatomic) NSMutableString* firstName

I posted a bit of code that was working for me. But mistook the getter for the setter. (I am still new, and tired :-) )

Now what is confusing is once it was pointed out that I was wrong. I re-looked at my code and realised what I had done.

But in my project I only had the setter synthesised and not declared.

@synthesize firstName =_firstName;

And I had declared the getter like so:

-(NSMutableString *)firstName{
    if (!_firstName) _firstName = [[NSMutableString alloc]init];

    return _firstName;
}

But all was working with no issue without me declaring a setter. Which your supposed to do for a property for a mutable object and (copy)

If put a setter in :

-(void)setFirstName:(NSMutableString *)mutableString{

    _firstName =  mutableString ;

}

It still works all ok. I use the call:

[self.firstName setString:@"some words"];

I did get the Exception once when I think I first removed the getter and leaving the setter. But I cannot repeat the error!

I hope this is clear..

Does any one know what is going on. And am I doing the setter and getter correctly in this case.

Thanks

Community
  • 1
  • 1
markhunte
  • 6,805
  • 2
  • 25
  • 44

2 Answers2

2

The setter you wrote is incorrect for the reason that it doesn't make a mutable copy of the string which is passed in.

But I think your real question is why you don't see an exception here. The reason is: you haven't actually used the bad setter in the code you posted here.

Here is the code you posted:

[self.firstName setString:@"some words"];

This is basically the same as this:

NSMutableString *tmp = [self firstName]; // this is using your getter
[tmp setString:@"some words"]; // this is calling a method on NSMutableString

So first you use your getter, which correctly returns an NSMutableString*. Then you call a method that is already defined in Foundation called -[NSMutableString setString:]. This works fine because you are in fact sending it to an NSMutableString. So far you have avoided any issue.

Where you would have gotten an exception is something like this:

// assume myObj is an instance of whatever class has this 'firstName' property

[myObj setFirstName:@"Some static and immutable string"]; 
// OOPS! Now we've used the broken setter 
// and firstName is now NOT a mutable string!

[myObj.firstName setString:@"Some other string"]; 
// ERROR. Now we've tried to send setString to an immutable string object. 

I hope that helps.

BTW, it sounds like you are using ARC. In that case, a correct setter can be as simple as this:

-(void)setFirstName:(NSMutableString *)someString{

    _firstName =  [someString mutableCopy] ;
}
Firoze Lafeer
  • 17,133
  • 4
  • 54
  • 48
  • Still getting my head around it. Thank you for the clarification. I see I should have been using setFirstName: instead of the foundation defined setString. But when I try and use the call to it I can not seem to do [myObj setFirstName:@"Some static and immutable string"]; with firstName. I know I am being thick here. But how do I do this ?? – markhunte Jan 23 '12 at 10:36
  • Previously it looks like you were testing within another method in the same class. (`self.firstName setString:...`). You could do the same thing in that same method with `[self setFirstName:...]` or just `[self.firstName = @"some immutable string"]`. – Firoze Lafeer Jan 23 '12 at 19:22
  • I have tried both of those. No joy I get warnings about NSMutableString does not Declare setFirstName with [self.firstName = @"some immutable string"] , and for [self setFirstName:...] I get a warning about NSString being passed to NSMutableString. I can cast it and that warning goes away. But I feel I am still doing something wrong? – markhunte Jan 23 '12 at 19:41
  • Yes, you are doing something wrong. I thought that was the whole point? You wanted to make the exception happen as a result of your bad setter so you can understand how this all works. The compiler is warning you that you're about to do something wrong. Are you trying to do something else now? – Firoze Lafeer Jan 23 '12 at 20:47
  • Sorry. I realised I was doing something wrong but could not understand why it was looking like it was working. The goal is to understand how to do getters and setters right. And along the way understand why I was misleading myself in thinking I was doing it right when I was not. I feel like I have managed to not only confuse myself but everyone else..:-( – markhunte Jan 23 '12 at 21:06
1

Given a property declaration:

@property (copy,nonatomic) NSMutableString* firstName;

In non-ARC code the setter should be implemented like this:

- (void) setFirstName: (NSMutableString *) newString
{
    if ( _firstName != newString ) {
        [_firstName release];
        _firstName = [newString mutableCopy];
    }
}

or like this:

- (void) setFirstName: (NSMutableString *) newString
{
    [_firstName autorelease];
    _firstName = [newString mutableCopy];
}

A simple assignment won't do because without copying the new value you will crash. A simple copy won't do either because the ivar is a mutable string.

Costique
  • 23,712
  • 4
  • 76
  • 79
  • Thanks. I think I under stand the logic there. Check if the object is nil, release it. Make a new mutable copy. But why is mine working at all?? i.e no exception for the setString. And do you need to do release in iOS 5. Thanks again – markhunte Jan 23 '12 at 04:42
  • I just got a red warning for the release. ARC – markhunte Jan 23 '12 at 04:46
  • Constant strings like `@"some words"` are not autoreleased, that's why your setter appears to work. iOS 5 doesn't cancel the need for memory management. ARC does explicitly prohibit using `release` and `dealloc`. But even if you use ARC, I strongly recommend reading Apple's docs on reference count memory management because IMHO sometimes ARC rules may be very confusing even to experienced developers. – Costique Jan 23 '12 at 04:51
  • One clarification on above. Note that you don't need to test for whether the object is nil before releasing it; just release it. Objective C allows you to send messages to nil objects precisely so that you don't need to be doing this test all of the time. A message sent to a nil object does nothing. – Todd Masco Jan 23 '12 at 07:17
  • (More on sending messages to nil at [Apple's 'The Objective C Programming Language'](http://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/ObjectiveC/Chapters/ocObjectsClasses.html#//apple_ref/doc/uid/TP30001163-CH11-SW1) – Todd Masco Jan 23 '12 at 07:24
  • @ToddMasco Right. However, in the above non-ARC sample code I'm testing a different thing: if the value object doesn't change, don't do anything. This is important because you may deallocate the value object if `_firstName == newString`. You _can_ skip the test, but you will have to replace `release` with `autorelease`. Of course, all of this is valid only for non-ARC code. – Costique Jan 23 '12 at 07:30
  • @Costique I understand, I'm responding to markhunte's first comment where he asks about testing for nil first. – Todd Masco Jan 23 '12 at 07:45