PDA

View Full Version : I don't understand this: NSString question about what a book said:




KarlJay
Aug 18, 2012, 04:16 AM
Book: Objective-C Developer reference, Wiley Pub, 2011, ISBN: 978-0-470-47922-3 Author: Jiva DeVoe

Chap: 12 Page 265 'Using Strings'

Any two declarations of the exact same string value, even if stored
in different variable names, point to the same object. Therefore,
you can use these strings for keys, for example, where the equality
of the string as compared to another instance of that string will
be considered to be equal, both using the -isEqual: object
method as well as the == operator, which will compare the value
of the pointers.

Listing 12.2
Examining string constant equality
NSString *string1 = @”this is a string”;
NSString *string2 = @”this is a string”; // same object as string1
NSString *string3 = [NSString stringWithString:string1]; // makes new
assert(string1 == string2); // true
assert([string1 isEqual:string2]); // also true
assert([string1 isEqual:string3]); // true
assert(string1 == string3); // false

Ok, so it says: "Any two declarations of the exact same string value, even if stored in different variable names, point to the same object."

So I do this:
NSString *string1 = @"Number One";
NSString *string2 = @"Number One";
string2 = [string2 stringByAppendingString: @"New Text"];
NSLog(@"String 1 %@", string1);
NSLog(@"String 2 %@", string2);

string1 = Number One
string2 = Number OneNew Text

If string1 and string2 point to the SAME object, then why when one is changed, the other doesn't change?

And how in the world do they both point to the same object only based on string content?:confused:

Or is it that the pointers WERE the same, and now that one string changed, the pointer changed? if so, that's just strange.

Edit: Ok, just checked, with this code:

NSString *string1 = @"Number One";
NSString *string2 = @"Number One";
if (string1 == string2)
NSLog(@"Strings are ==");
string2 = [string2 stringByAppendingString: @"New Text"];
if (string1 == string2)
NSLog(@"Strings are STILL ==");

NSLog(@"String 1 %@", string1);
NSLog(@"String 2 %@", string2);

They are == on the 1st if() and NOT == on the second if(), so changing a string, changes it's pointer and if two strings are the same, they'll be given the same pointer?

Edit2:
Ok, so what happens if I have two strings NOT the same, then within the program, they become the same, have I just lost a pointer? And what happens if that pointer was used specifically in another part or another thread of the same program? What if I store the value of a pointer to a var, the pointer then changes... does the var change?



Reason077
Aug 18, 2012, 05:03 AM
NSString *string1 = @"Number One";
NSString *string2 = @"Number One";
string2 = [string2 stringByAppendingString: @"New Text"];
NSLog(@"String 1 %@", string1);
NSLog(@"String 2 %@", string2);

string1 = Number One
string2 = Number OneNew Text

If string1 and string2 point to the SAME object, then why when one is changed, the other doesn't change?

And how in the world do they both point to the same object only based on string content?:confused:


string1 and string2 point to the same object at compile time. So the value of (string1 == string2) will be YES.

But when you call "string2 = [string2 stringByAppendingString: @"New Text"];" you are creating a NEW string object, and assigning that new object to the string2 variable.

----------


Ok, so what happens if I have two strings NOT the same, then within the program, they become the same, have I just lost a pointer?

String are only guaranteed unique at compile time. At runtime you could indeed end up with multiple NSStrings with the same content.

KnightWRX
Aug 18, 2012, 06:39 AM
Ok, so it says: "Any two declarations of the exact same string value, even if stored in different variable names, point to the same object."

And how in the world do they both point to the same object only based on string content?:confused:

The book doesn't make it quite clear, but it's not based on value or content, it's based on the fact you're initially pointing to what is known as a string literal, an object created by the compile based on the static string you put in your source code. Since both literals are the same, the compiler does not create 2 objects, only 1.

When you using any NSString method that returns a new string, your pointer will obviously not point to the string literal anymore. That literal is constant and cannot be modified. Also, NSStrings cannot be modified, you have to use the NSMutableString class to get a modifiable string. Any "appends" to an NSString ([string stringByAppendingString]) returns a new pointer. You should never write code like the following :


string2 = [string2 stringByAppendingString: @"New Text"];


That could be a memory leak.

Reason077
Aug 18, 2012, 06:54 AM
string2 = [string2 stringByAppendingString: @"New Text"];


That could be a memory leak.

How? Even if not using ARC, there is no memory leak here. You're replacing an NSString constant with a autoreleased NSString.

Without ARC, it's a bit sketchy to write code like that in case the initial value of string2 was a retained object. But with ARC it's totally fine.

KnightWRX
Aug 18, 2012, 07:02 AM
How? Even if not using ARC, there is no memory leak here. You're replacing an NSString constant with a autoreleased NSString.


In this particular case, it isn't. However, getting used to writing such code if not using ARC could lead to a memory leak. The use of the conditional ("could be a memory leak") was on purpose here.

The OP seems to be under the impression that stringByAppendingString modified the string object. It creates a new one. If you were to apply the same code to a normally init'ed string, you now have a leak. If you want a modifiable string and want to append to it, you use NSMutableString's instance method appendString or appendFormat.

Just have to break bad habits before they form. You should never return the new string created by the method into the pointer you used to create it.

Reason077
Aug 18, 2012, 09:01 AM
Just have to break bad habits before they form. You should never return the new string created by the method into the pointer you used to create it.

Before ARC, I would agree with you. But there's no issue with doing this with ARC code.

KnightWRX
Aug 18, 2012, 09:06 AM
Before ARC, I would agree with you. But there's no issue with doing this with ARC code.

Sure, but if you start mixing it up, you have to think about it. "Is this project migrated to ARC yet ?" Just adopt sensible habits and you'll never run into the issue.

Let's not encourage bad habits because the compiler fixes them for us. Let's understand the underlying concepts and write good code to begin with. That's what I prefer to pass down to up and comers. I don't see how you can disagree with that.

Duncan C
Aug 18, 2012, 10:47 AM
Book: Objective-C Developer reference, Wiley Pub, 2011, ISBN: 978-0-470-47922-3 Author: Jiva DeVoe

Chap: 12 Page 265 'Using Strings'



Listing 12.2
Examining string constant equality
NSString *string1 = @”this is a string”;
NSString *string2 = @”this is a string”; // same object as string1
NSString *string3 = [NSString stringWithString:string1]; // makes new
assert(string1 == string2); // true
assert([string1 isEqual:string2]); // also true
assert([string1 isEqual:string3]); // true
assert(string1 == string3); // false

Ok, so it says: "Any two declarations of the exact same string value, even if stored in different variable names, point to the same object."

So I do this:
[CODE]
NSString *string1 = @"Number One";
NSString *string2 = @"Number One";

--snip--


The compiler does some optimization, and recognizes that string literals that contain the same sequence of characters are actually the same data, so it only creates one string.

However, this is an optional compiler optimization. You should not write code that depends on it.

Today,



NSString *string1 = @"Number One";
NSString *string2 = @"Number One";

if (string1 == string2)
NSLog(@"The strings are the same");


Might work, and in a future release of the compiler, it might not. Also, if you change your code later so string2 is constructed from 2 separate words, it will fail.

That's what's known as fragile code.

Get in the habit of NEVER using == to compare string objects. Always use -isEqualToString.

Using == to compare NSObjects is almost never what you want. Many of Apple's object classes support the isEqual method, which compares the value of the objects, not the identity of the objects.

KarlJay
Aug 18, 2012, 03:09 PM
Get in the habit of NEVER using == to compare string objects. Always use -isEqualToString.

Using == to compare NSObjects is almost never what you want. Many of Apple's object classes support the isEqual method, which compares the value of the objects, not the identity of the objects.
Your right and great point. In this example I was actually wanting to compare the pointers because I was surprised that the pointers would be the same.

Depending on what you are doing, this optimization, could hinder. In other words, if the two strings become equal and unequal over and over, thats a lot of object creation.

chown33
Aug 18, 2012, 04:02 PM
Depending on what you are doing, this optimization, could hinder. In other words, if the two strings become equal and unequal over and over, thats a lot of object creation.

You're making a simple yet profound mistake here, and it's the cause of your confusion in the original post.

Two "strings" don't become equal or unequal, if they're immutable objects (which NSStrings are).

Two variables might become equal or unequal, as their values change over time. You're confusing the value, which is the actual memory address of the NSString object, with the variable, which holds a value.

A variable holds a value. It holds a new value when it's assigned a new value. It holds the last-assigned value for as long as the variable exists. The variable is not the value. The variable is simply the container that holds the value and allows you to name it in expressions.

Simple example:
int a = 5;
int b = 3 + 2;
The values of a and b are equal. The variables hold the same value. The values have the same bit-pattern.

The value of a (i.e. the value stored in a) is currently 5, but a is a variable, so that can change. It happens to match the value of b, but b is also a variable, and both variables can be assigned new values. Assigning new values may change the relationship of equality, but this should be expected since the names refer to variables, not constants. It is not the case that a will always be 5 and be will always be equal to it.

Suppose you then have this line of code:
b = 9 / 3;
Now a and b are not equal. The variables hold different values. The values have different bit-patterns. But this does not mean that 5 (the value in a) is now equal to 3 (the value in b). There's no surprise here, as there is in your first post. You need to look carefully at what's really happening to see why you're not surprised by the code with a and b, yet you are surprised by the code with string1 and string2.

Go back to your first example. Consider that your variables are both pointers to an NSString object. A pointer is an address. The variables hold the same value. The values have the same bit-pattern. But what is the value, exactly? It's a memory address; a number indicating an addressable memory location where the actual object data is stored. So when the variables are equal (using ==), the bit-pattern (i.e. the number signifying a memory address) in both variables is identical.

Now assign a different value to string2. What happens? A function/method is called, which returns a value. That value (a memory pointer) is stored into the variable. What is the memory pointer's actual numeric value? You'd have to print it as a number to see, but I can guarantee one thing: it will not be the same bit-pattern as the value stored in string1.

If that's confusing, try using the %p format-specifier (https://developer.apple.com/library/ios/#documentation/Cocoa/Conceptual/Strings/Articles/formatSpecifiers.html) with an NSLog to print the string1 and string2 values.


An array (a simple C array) is an example of multiple variables with a single name. Each slot of the array holds a distinct variable. You can read and write each slot separately, and the variables never overwrite one another. Each slot in the array is addressed by a name (the array name) and an index (an integer position). One name, multiple variables.

KarlJay
Aug 18, 2012, 06:34 PM
I don't think I was confused about the pointer to a string vs the string itself. I was confused about the "when the strings are the same, the pointers change to be the same"

Ok, let me try this:

str1 (a pointer to a string)
str2 (a pointer to a string)

"one two three" (a string)

str1 = "one two three" // str1 is a pointer to a string, the string is "one two three"

str2 = "abcdef" // str2 is a different pointer to a different string

str1 == str2 // NO pointers are NOT the same

str2 = "one two three" // str2 now has a different string that it had before

Now:
str1 is a pointer to a string "one two three"
str2 is a pointer to a string "one two three"

The pointers before were not equal and their string values were not equal.
the string value of str2 changed and is now equal to the string value of str1

The pointers were different, now the pointers are the same.

changing the value of a string CAN change it's pointer value ONLY when the value of the string matches the value of another string.

This is the part that I find confusing.

chown33
Aug 18, 2012, 08:09 PM
I don't think I was confused about the pointer to a string vs the string itself. I was confused about the "when the strings are the same, the pointers change to be the same"

Ok, let me try this:

str1 (a pointer to a string)
str2 (a pointer to a string)

"one two three" (a string)

str1 = "one two three" // str1 is a pointer to a string, the string is "one two three"

str2 = "abcdef" // str2 is a different pointer to a different string

str1 == str2 // NO pointers are NOT the same

str2 = "one two three" // str2 now has a different string that it had before

Now:
str1 is a pointer to a string "one two three"
str2 is a pointer to a string "one two three"

The pointers before were not equal and their string values were not equal.
the string value of str2 changed and is now equal to the string value of str1

You wrote pseudo-code, so I don't know how to answer. If you want to try writing real C or Objective-C, I can tell you whether the pointers will be the same or not.

Or you can write the code, compile it, use the %p format-specifier, and see for yourself if the pointers are the same.


The pointers were different, now the pointers are the same.

So what? Describe how this is a problem.


changing the value of a string CAN change it's pointer value ONLY when the value of the string matches the value of another string.

This is the part that I find confusing.
I don't understand what you mean by the underlined statement.

You're using terms like "the value of a string" in an unclear way. Do you mean "the characters stored at a particular memory address"? Because nothing is changing any characters stored at any memory address. A variable is changing, and it points to an object. But no objects have any of their contents changed.

I don't see what's confusing here. The compiler is simply making a safe optimization, by seeing that two variables refer to the same immutable object, which is an NSString object containing the characters sequence "one two three".

Suppose the compiler didn't do this, what would happen? You'd have separate two objects, each with the contents "one two three". The contents would be the same, so isEqual: would return YES, but the pointers would be different (two separate objects), so == would be false.

The compiler is only permitted to do this because an NSString is immutable. That NSString's contents, i.e. the sequence of characters "one two three", is immutable. If the contents could be changed in the same object, then the compiler couldn't do this optimization. "In the same object" means the object pointer is the same before and after the change. Only NSMutableString is mutable, though, and no @"" literal is an NSMutableString.


In this Objective-C:
NSString * str1 = @"one two three";
NSString * str2 = @"five";

There are two NSString literal objects (note the terminology: two objects). There are also two variables, and each variable holds a pointer to a different object. The two bit-patterns will be different, because the memory at a single address can only hold one thing at a time.

Now consider this code:
NSString * str1 = @"one two three";
NSString * str2 = @"one two three";

There is one NSString literal object. There are two variables, and they both have the same value: the address of the single NSString literal. The two bit-patterns in the variables will be the same, because they both point to the same NSString object. The compiler can do this because it sees it's already defined an NSString object with contents "one two three", so it simply reuses the same object for str2.

Since the object is immutable (as all NSStrings are), there is no harm that can arise from sharing the single string object. No code can change the object and cause any other code to suddenly have a string that isn't "one two three". That's because no code can change the object at all: it's an immutable object.

That literal NSString object will never change. Its memory is fixed. It will never be dealloc'ed and used to make another object, so its memory address will never be used for any other object. Since only one thing can occupy a given memory address at a time, there is zero chance that something else will appear instead of that object.

The code can make new objects, and assign those object pointers to existing variables, e.g. assign a new value to str2. But that's obviously assigning a new value to a variable. It has no effect at all on the contents of the literal NSString object, and also has no effect on the value of the variable str1.

If you assign another object pointer to str1, you'll have no pointers left in any variable that point to the literal NSString object. It still won't be reclaimed (dealloc'ed), which might technically make it a memory leak, but it's not a recurring leak so it's not a problem.


I still think you should just write some real code, use %p to see pointer values, and see what happens. If you write something you don't understand, then post the code, describe what you expected to happen, describe what actually happened, and we'll try to explain.

I think if you look at it in terms of variables that hold values, and those values are addresses of objects in memory, it will eventually make sense. You need to see some real addresses, i.e. real hex numbers, to see what happens.

KarlJay
Aug 18, 2012, 08:43 PM
I think this whole thing just sunk in with your last post.

The problem I was having was the usage of string. ObjC has NSString and NSMutableString. If I do the exact same thing using NSMutableString, I should get the results I expected.

I wasn't thinking of NSString as a literal object or fixed object.

Thanks!