I have been experimenting a bit with Objective-C and noted some, at least to me, rather strange behavior. First I define a pointer to an NSString and add it to an NSArray:
NSString *s = #"A";
NSArray *a = [NSArray arrayWithOject: s];
I then print out the value of s as well as the contents of a:
NSLog(#"%#", s);
NSLog(#"%#", myArray);
and in both cases the output is A. Now, if I change the pointer s, say
s = #"B";
then the two NSLog statements print out B and A, respectively.
That is, the pointer in my array still points to #"A". After spending years coding in Java, this is very surprising to me. Am I missing something really fundamental here?
Thanks,
Michael Knudsen
The easiest way to understand this is that #"A" creates a new NSString object.
In your code, you set the pointer s to point to this object, and then add the original object (not the pointer) to an array. You then change the address that the pointer points to to a new address.
If you want to change the original object, then use NSMutableString and modify the actual object (instead of changing the pointer to a new object) and they will both update as you expect.
Try:
NSMutableString *s = #"A";
// Add to array
[s setString:#"B"]
That is because NSStrings are immutable, as in Java, so you are not changing the contents of the object stored at address x (the one nsarrsy has), you are pointing s to address y.
The Java and Objective-C behaviors are the same. With the following Java code:
String foo = "foo";
Vector myVector = new Vector();
myVector.add(foo);
System.out.println(foo);
System.out.println(myVector);
foo = "bar";
System.out.println(foo);
System.out.println(myVector);
The following values are printed out:
foo
[foo]
bar
[foo]
Note: The same is true if I use String[] instead of Vector, but an NSArray is more like Vector than String[].
The easiest way to see this is to see that your s = #"B"; simply point s to another string object rather than modifying the original object.
So the array still contain #"A". When you add objects to NSArray you simply make a pointer in NSArray to point to #"A". That never changes.
Related
i want to ask a question on NSString, the question is if
NSString str = #"Hello";
str = [str stringByAppendingString:#"World"];
if we NSLog the str we would get an output - HelloWorld!
So my question is if str is NSString class variable an its an static one (which can not be changed once it is defined) then how can we able to change it, (Note that I have used same NSString object str).
You haven't declared the string as static, but NSString is immutable. You are creating a new string and replacing str. Many Obj-C classes have a mutable type, so if you intended to modify your string as in this example, you might want something more like:
NSMutableString *str = [NSMutableString stringWithString:#"Hello"];
[str appendString:#" World"];
Note, #"Hello" is a NSString, so attempting to initialize a NSMutableString using that syntax would result in an error.
Actually, static variables can be changed once they are defined. Static variables are simply maintained throughout all function/instance calls. You can find a more detailed explanation here static variables in Objective-C - what do they do?. So the result would be the same. If the variable were defined as const (which cannot be changed) then you would get a compiler error. Generally speaking, the best way to figure these types of things out is to just try them on your own. If it's just a matter of writing a couple lines of code, or changing a keyword, what does it hurt?
Changing a mutable strings value in many languages is very easy:
myString = "foo"
myString = "bar"
However, while learning Objective C it seems like you have to really, really jump through hoops to change an existing string's value (This is often done with switch or if cases):
NSMutableString *myString;
myString = [NSMutableString stringWithString:#"foo"];
myString = [NSMutableString stringWithString:#"bar"];
Is there an better way to change a string's value?
myString = #"foo"
myString = #"bar"
is fine in obj-c too. Don't confuse changing object value (for which you need NSMutableString) and variable value.
EDIT. Well, maybe you really need to change existing object value, but it's unclear from your question. You don't usually need to mutate existing object for switches.
Objective-C doesn't force you to jump through any extra hoops relative to most languages. It's probably simply less clear what you're doing in other languages perhaps, because for the most part, it's the same.
For starters, Objective-C doesn't allow objects on the stack--only on the heap. This is true for all objects and there's nothing special about strings here. Other languages such as C++ allow objects on the stack.
When we create objects on the heap, our variable is merely a pointer to that object, so when we create an object we have to allocate memory for it.
When we assign a string object via the = operator, we've change the memory location our variable points too.
You can create strings as simply as this:
NSString *fooString = #"foo";
NSString *barString = #"bar";
Which really isn't any more complex then any language I've seen. It's just an extra #, how is this complicated?
CHANGING the value of a string, however, isn't so simple. NSString is immutable, so to change it's value, we have to create a new object on the heap, and point to that. We haven't changed the value, we've created a new object and pointed at that.
With NSMutableString however, there are a range of available methods for changing the actual value at the memory location we point to.
In your example
NSMutableString *myString;
myString = [NSMutableString stringWithString:#"foo"];
myString = [NSMutableString stringWithString:#"bar"];
the first two lines can be grouped together and the third can simply reduce to changing the string property of the mutable string, like this
NSMutableString *myString = [NSMutableString stringWithString:#"foo"];
myString.string = #"bar";
Anyway, mutable strings actually make sense only when you need to pass a reference away and allow it to be changed.
If you simply need to change the value of a NSString * pointer over time, just do
NSString *myString = #"foo";
myString = #"bar";
myString = #"baz";
and be done with it.
It depends on whether you insist on having a string where you can change the bytes inside it or only want to replace it entirely. Replacement of the whole thing doesn't need 'mutable' but if you'd like to replace it and be able to modify the bytes, then you want something like:
NSMutableArray *mstr = [#"foo" mutableCopy];
mstr = [#"bar" mutableCopy];
Imagine I define a variable like this
NSString * a = #"Hello people";
And now I copy the pointer to b so that both a and b point to the location where #"Hello people" is stored in memory (right? Or do I misunderstand how this works?)
NSString * b = a;
Now I want to change b such that a is also updated with the new value. The following does not work since it will point b to a new NSString #"Thanks all"
b = #"Thanks all!";
NSLog(#"%#", a); // still "Hello World"
but what I want is to replace whatever is at the memory location of #"Hello people" so that all pointers to it will update as well.
b = ????;
NSLog(#"%#", a); // Whoa, it still points to the same memory location as b!
Thanks!
In this case you want to use NSMutableString instead of NSString.
Because an NSString is immutable, when you change the original, it points to a whole new instance. But if you use an mutable version, the pointer remains unchanged even if the contents change.
However - although this will work, it's a bad idea. Such mutability is fragile. For example, if you have any KVO observers on this property, if the string changes, then no KVO notifications are fired.
Edit
An alternative is to use multiple indirection:
NSString **string;
&string = #"some string";
Now you can have:
NSString **string2 = string;
And changes to string will be propagated to string 2, because although the pointer is the same, the object that it points to will have changed.
Hovewever, you'll need to dereference the pointer to use it, i.e.
NSLog (#"String is: %#", *string);
or
NSLog (#String2 is: %#, *string2);
But you still have that mutability issue.
The book I'm currently reading has me write the following code :
-(IBAction)displaySomeText:(id)sender {
NSString *cow = #"Milk";
NSString *chicken = #"Egg";
NSString *goat = #"Butter";
NSArray *food = [NSArray arrayWithObjects:cow, chicken, goat, nil];
NSString *string = #"The shopping list is: ";
string = [string stringByAppendingString:[food componentsJoinedByString:#", "]];
[textView insertText:string];
}
I understand somewhat how arrays work but I need help understanding the following code
string = [string stringByAppendingString:[food componentsJoinedByString:#", "]];
I have never ever seen an instance where this is possible.
He has me create a 'string' object, from the NSString class, and then I'm doing this
string = [string stringByAppendingString:];
I'm confused. I have never seen an example where I create an object and then perform a method on the same object and store it in that exact same object.
For example, I know I can do this
NSSound *chirp;
chirp = [NSSound soundNamed:#"birdChirp.mp3"];
the above makes sense because I used the created object and performed a class method on it..
but I always assumed that the equivalent of the following code was NOT possible
chirp = [chirp methodNameEtc..];
I hope I explained my question well. If not I could always elaborate further.
I think this is the heart of your question "I'm confused. I have never seen an example where I create an object and then perform a method on the same object and store it in that exact same object."
To answer that question, your not actually 'storing it in the exact same object'. What you are doing is confusing pointers and objects.
Let's just look at this line:
string = [string stringByAppendingString:#"Hello"];
In this line 'string' is a pointer, not the object it points to. What this line of code is saying is:
"Object currently referenced by the pointer 'string', return to me a new NSString object whose text is your text with this text added. And when I get that new NSString object I ordered make the pointer 'string' point to that object instead."
string = [string stringByAppendingString:[food componentsJoinedByString:#", "]];
is the same as
NSString *tmpString = [food componentsJoinedByString:#", "];
string = [string stringByAppendingString:tmpString];
So in the example, the innermost square brackets are evaluated first, and then the outermost. Does that clarify it a bit? Thing of it like parentheses in math: (1*2*(1+2))... the innermost () get evaluated before you can determine that the real problem is 1*2*3. That is what is happening with [food componentsJoinedByString:#", "].
stringByAppendingString is not storing the string in the exact same object. It's one NSString object creating a new (autoreleased) NSString object.
There is no equivalent example for chirp (which is a NSSound object), unless you create your own category which extends NSSound with a method to return a brand new NSSound object.
Makes sense?
Oh, I also appreciate your Takizawa icon.
When should I use copy instead of using retain? I didn't quite get it.
You would use copy when you want to guarantee the state of the object.
NSMutableString *mutString = [NSMutableString stringWithString:#"ABC"];
NSString *b = [mutString retain];
[mutString appendString:#"Test"];
At this point b was just messed up by the 3rd line there.
NSMutableString *mutString = [NSMutableString stringWithString:#"ABC"];
NSString *b = [mutString copy];
[mutString appendString:#"Test"];
In this case b is the original string, and isn't modified by the 3rd line.
This applies for all mutable types.
retain : It is done on the created object, and it just increase the reference count.
copy -- It creates a new object and when new object is created retain count will be 1.
Hope this may help you.
Copy is useful when you do not want the value that you receive to get changed without you knowing. For example if you have a property that is an NSString and you rely on that string not changing once it is set then you need to use copy. Otherwise someone can pass you an NSMutableString and change the value which will in turn change the underlying value of your NSString. The same thing goes with an NSArray and NSMutableArray except copy on an array just copies all the pointer references to a new array but will prevent entries from being removed and added.