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

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

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

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.

Related

Pass by value vs Pass by reference

I have been looking into some basics over the last couple days and I realized that i never truly understood why pass-by-reference for NSString/NSMutableString did not work.
- (void)testing{
NSMutableString *abc = [NSMutableString stringWithString:#"ABC"];
[self testing:abc];
NSLog(#"%#",abc); // STILL ABC
}
-(void)testing:(NSMutableString *)str {
str = [NSMutableString stringWithString:#"HELP"];
}
How do i go about this? I want my testing method to be able to manipulate the String from the main method. I have been using this with Mutable Arrays, dictionary etc and works fine. Feels strange that I never realized how this works with Strings.
But the value gets changed in something like this which is a reference to the first string
NSMutableString *string1;
NSMutableString *string2;
string1 = [NSMutableString stringWithString: #"ABC"];
string2 = string1;
[string2 appendString: #" HELP"];
NSLog (#"string1 = %#", string1); // ABC HELP
NSLog (#"string2 = %#", string2); // ABC HELP
Like Java, Objective-C has only passing and assigning by value. Also like Java, objects are always behind pointers (you never put the object itself into a variable).
When you assign or pass an object pointer, the pointer is copied and points to the same object as the original pointer. That means, if the object is mutable (i.e. it has some method that mutates its contents), then you can mutate it through one pointer and see the effects through the other one. Mutation is always achieved by calling a method, or assigning to a field directly.
-(void)testing:(NSMutableString *)str {
[str setString:#"HELP"];
}
Assigning to a pointer never mutates the object it points to; rather, it makes the pointer point to another object.
I cannot in good conscious let this wrong answer linger on the internet.
Pass by reference in objective c is POSSIBLE; which is why it is better than Java.
Here's how:
- (void)testing
{
NSMutableString *abc = [NSMutableString stringWithString:#"ABC"];
[self testingHelper:&abc];
NSLog(#"%#", abc); // NOW HELP
}
- (void)testingHelper:(NSMutableString **)str {
*str = [NSMutableString stringWithString:#"HELP"];
}

Why does my NSString becomes CGPath? Xcode, Titanium

So the short of it is I want to define a global string variable that I can reference whenever. The function that I reference it in, it returns a string. As soon as I store it and reference it in another function it outputs as <CGPath 0x5bbf50>
What the heck? The code is below and keep in mind this is a module for Titanium.
First, the definition of the global variable..
#interface ComTestModule : TiModule <CBCentralManagerDelegate, CBPeripheralDelegate>
{
NSString * teststring;
}
The next part is the function where I first send the string variable from titanium to xcode..
-(void)setService:(id)args{
ENSURE_ARG_COUNT(args, 2);
teststring = [args objectAtIndex:0];
NSLog(teststring);
}
The output of the NSLog displays the actual string that was passed.
Now the final function where I call the string again and attempt to output it to the log..
- (void)peripheral:(CBPeripheral *)peripheral didDiscoverServices:(NSError *)error
{
NSLog(#"---%#", teststring);
}
As I said before, during this step it outputs as ---<CGPath 0x3ef4e0>
I'm really not sure what's going on.. Any help at all about getting this to return as the original string instead of the CGPath would be great!
Effects like this typically happen when you store a pointer to an object that was released and deallocated. The runtime will then replace that chunk of memory that previously held an NSString instance with, in this particular case, a CGPath instance.
If you want to ensure that your NSString stays alive, you need to take ownership of it. You can do that by either retaining it or copying it. Copying is the preferred method when talking about strings, so try replacing this line:
teststring = [args objectAtIndex:0];
With this:
teststring = [[args objectAtIndex:0] copy];
Now just be sure to release it when you're done.
The other poster's suggestion was good. You might want to make your testString variable into a copied property:
In your .h file:
#property (nonatomic, copy) NSString teststring;
And in your .m file
#synthesize teststring;
Then when you assign to it, use code like this:
self.teststring = [args objectAtIndex:0];
That "dot syntax" invokes the setter for your property rather than changing the instance variable directly. Since you declared your property with the "copy" qualifier, the setter method copies the string before putting it into the instance var.
Finally, you would add this code to your dealloc method:
self.teststring = nil;
The setter method also releases any old value in the property before assigning a new value, so setting it to nil releases the old value.

What's 'void' on NSArray?

I defined a NSArray in a header file like this:
#property (nonatomic, retain) NSArray *ages;
In my implementation I want to set this variable like this:
ages = [self setAges:[ageValues allKeys]];
'ageValues' is a NSDictionary. So what I do is just setting the array of keys to my self-defíned array. Strange enough, I get the following error message:
Asssigning to 'NS Array *' from incompatible type 'void'
But where can I find something void here? In my opionion I am just setting another array ([ageValues allKeys) to my own array and I can't find anything void???
The setAges: method is a method that returns void, in other words: it returns nothing (not even nil or something; it literally is not returning anything). Now you cannot assign "nothing" to a variable.
That being said, your code wants to do the same thing twice. All you want to do is simply:
self.ages = [ageValues allKeys];
or:
[self setAges:[ageValues allKeys]];
They do exactly the same, but use different syntax (the compiler transforms the first into the second).
setAges is a void method since it's a setter. As such, it returns void and you're then trying to assign it to your ages member. All you need to do is call setAges.
[ self setAges:[ ageValues allKeys ] ];
self.ages = [ageValues allKeys];
or
ages = [[ageValues allKeys] retain];
[self setAges:[ageValues allKeys]]; returns void.
If you have synthesized your ages property in your .m like so:
#synthesize ages;
The setter is automatically generated for you, so all you need to do is
self.ages = [ageValues allKeys];

Difference bewteen declaring a NSString with alloc and init and assigning with #"myString"

Currently I'm troubled with a small understanding issue on Objective-C. As example following Aaron Hillegass's book, I'm wondering about assigning an NSString in the init method of a class a value like in this example (For people who know the book, this is used in the Person class of RaiseMan):
- (id)init
{
if(![super init])
return nil;
myString = #"New entry";
return self;
}
This string isn't allocated by me, so normally I shouldn't bother about releasing it.
BUT! What happens in a setter-method of this string? Following the memory management rules the method should look like:
- (void)setMyString:(NSString *)newString
{
if(myString != newString) {
[myString release];
[newString retain];
myString = newString;
}
}
Why does [myString release] work? I've read somewhere, that with = #"bla" assigned strings can't be released.
And is initializing with = #"bla" the right way? Or should I use alloc and init instead?
Thanks for any help :)
NSString *constantString = #"constantString";
String like constantString are said to be from a private(?) class NSConstantString and they are alive through all your program life. Off-course release and retain work, (in the mean that they won't give you a exception or crash) They just do nothing.
Read more here
Also you said in one of your comments that it would be a{#property(..., copy) NSString myString;But what you are showing us is a typical #property(..., retain)
AFAIK, string constants of the form #"..." are actually a child class of NSString that redefine retain and release as no-ops. This allows the compiler to store those string constants in the data segment of your executable instead of on the heap.
Is myString declared in the header-file? Like: #property(nonatomic, retain) NSString myString. If that is the case, then myString is retained. Otherwise, it's not necessary to release it.

If I want to make a new instance of an object in a function whose pointer is passed by reference in it

- (void)createAString:(NSString **)str
{
*str = [NSString stringWithString:#"Hi all!"];
[*str autorelease]; // ???? is this right ?
}
How should I use release or autorelease ? I don't want to release outside of the function of course :)
...
NSString *createStr;
[self createAString:&createStr];
NSLog(#"%#", createStr);
You're correct that you'd generally want to return autoreleased (or the like) objects from out params when you use this form. Your assignment statement in the function that sets *str to a string:
*str = [NSString stringWithString:#"foo"];
is already doing the right thing, because that method returns an instance of NSString that the caller doesn't own. Just like you could return this string object from your function without any further memory management, you can set it as the outparam as you've done. Your second snippet showing the call site is fine.
This said, I'm worried about a few things in your code that you should be sure you understand:
The value of str inside the method is still a **, and sending that a message (as you've done for the speculative autorelease) is nonsense. Be sure you fully understand doubly indirected pointers before using them too liberally. :) If you need to send str a message after creating it, send it to *str, which is what contains the NSString *.
Setting an outparam like this when the function returns void is not idiomatic Cocoa. You would normally just return the NSString * directly. Outparams are rare in Cocoa. (Usually just NSErrors get this treatment from framework calls. Otherwise they conventionally use name like getString to differentiate them from normal get accessors which don't use the word "get".)
I hope -stringWithString was just an example. That method is almost never used in practice, since it's equivalent (in this case) to just using a #"string literal" (although that would muddy your example).
Instead of using a double pointer, would it not be more elegant to use an NSMutableString instead?
- (void)createAString:(NSMutableString *)str
{
[str setString:#"Hi all!"];
}
....
NSMutableString *createStr = [[NSMutableString alloc] init];
[self createAString: createStr];
NSLog(#"%#", createStr);
[createStr release];
Or, even better, just have the createAString method return an NSString.
- (NSString *)createAString
{
return #"Hi all!"; // this is autoreleased automatically
}
I wouldn't want to presume that your needs are this simple, though. =)