Why am I NOT getting an Exception with this getter and setter for a NSMutableString Property

444 Views Asked by At

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

2

There are 2 best solutions below

5
On BEST ANSWER

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] ;
}
7
On

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.