Something is different between #"" and #"123"? - objective-c

when I test method "retainCount", I met a question, just as below:
NSString *s_afmt0 = [[NSString alloc] initWithFormat:#""]; //-1
NSString *s_afmt1 = [[NSString alloc] initWithFormat:#"123"]; //1
NSLog(#"s_afmt0:%d", [s_afmt0 retainCount]);
NSLog(#"s_afmt1:%d", [s_afmt1 retainCount]);
result :
s_autf0:-1
s_autf1:1
I don't know why? why s_afmt0's retainCount is -1, and s_autf1's retainCount is 1. What is the difference between #"" and #"123"? anyone can explain ? thanks

you're printing the retainCount as a signed integer, when in fact it is an unsigned integer (type NSUInteger with a format specifier of %u, not %d). -1 is equivalent to UINT_MAX, and means the object has no retainCount. It is likely that NSString has recognized that the given string is immutable, and assigned the pointer to the static #"" empty NSString, rather than placing one on the heap.
In fact, you can confirm this with the following code:
NSString* str1 = [[NSString alloc] initWithFormat:#""];
NSString* str2 = [[NSString alloc] initWithFormat:#""];
NSLog(#"string1: %p, string2: %p", str1, str2);
The above code should print the same pointer address for both str1 and str2, confirming my theory that NSString optimizes this particular case out, and indeed it does:
06:46:30.142 StringTest[45214:303] string1: 0x7fff7d8acf90, string2: 0x7fff7d8acf90
So let's see if this is specific to NSString. It should be, otherwise all NSMutableString objects initialized to an empty string would point to the same memory!
NSMutableString* str1 = [[NSMutableString alloc] initWithFormat:#""];
NSMutableString* str2 = [[NSMutableString alloc] initWithFormat:#""];
NSLog(#"string1: %p, string2: %p", str1, str2);
Outputs:
06:53:36.688 StringTest[45278:303] string1: 0x10010a850, string2: 0x10010a890
Two different memory locations. So there you have it, a neat little optimization in the Foundation framework. TIL
Edit:
As bbum points out, you should never rely on retainCount, and this is an example of where things can go wrong if you do

While you should never use retainCount, the most likely reason for what you are seeing is that the empty string #"" is being treated special by the compiler since it is such a common literal.
Also, why are you using string formats with string literals? I suppose this is just for testing purposes but you should just have:
NSString *s_afmt0 = #""; // no need to use stringWithFormat here

Related

NSString Address issue

I'm trying to print address to string but i'm getting different address in first NSLog & same address in second NSLog. So could you tell me how is this happing. Its really confusing me. Thanks a lot in advance for your efforts.
NSString *str1 = [[NSString alloc] init];
NSString *str2 = [[NSString alloc] init];
NSString *str3 = [[NSString alloc] init];
NSLog(#"str1 = %p , str2 = %p, str3 = %p",&str1,&str2,&str3);
NSLog(#"str1 = %p , str2 = %p, str3 = %p",str1,str2,str3);
Output
str1 = 0x7fff565b9c88 , str2 = 0x7fff565b9c80, str3 = 0x7fff565b9c78
str1 = 0x10c0a7060 , str2 = 0x10c0a7060, str3 = 0x10c0a7060
I don't understand why str1, str2, and str3 all point to the same memory location.
And why should str1, str2, str3 all reside at different memory addresses? They're all the same immutable string.
See bbum's comment here:
Right... one implementation detail of relevant interest (but, by no means, invalidates the answer in anyway); [[NSString alloc] initWithString:#"Hello world"] won't actually create a string on the heap. It'll just return the __NSCFConstantString (or whatever it is called) that was laid down in the mach-o file by the compiler. It is merely an interesting detail in that it does not change anything about your consumption of said string; it should be treated just like any other object.
Emphasis mine.
What's going on here is that when the compiler can determine at compile time what an immutable NSString object will be, it's creating that string differently. As bbum states, ultimately it's an implementation detail that you shouldn't worry about when you're writing your program.
But the side effect of this means that the compiler is able to make my program more memory efficient because it is able to find all of these instances and make all of my NSString pointers that it knows are supposed to be holding the same immutable value all point to the same single memory address.
We can probably achieve the same result with the following:
NSString *str1 = [[NSString alloc] init];
NSString *str2 = [NSString new];
NSString *str3 = [[NSString alloc] initWithString:#""];
NSString *str4 = [NSString stringWithString:#""];
NSString *str5 = #"";
These are all effectively the same thing.
However, if we create another string:
NSString *str6 = [NSString stringWithFormat:#"%#", #""];
This will (most likely... last time I checked) end up with a different value if we print str6 as a pointer.
And there are other ways to generate immutable NSString objects that don't get optimized like this at compile time. The point here is that if the compile can know at compile time what the string is going to be, it will create a __NSCFConstantString in the background that's outside of memory management, and it will point to that single instance whatever it can. Once it gets to run time, it will only point anything else to this if you point it there directly (str6 = str1). Otherwise, it's not going to waste execution time trying to determine if the strings are equal. If a new NSString happens to be equal and it wasn't happened at compile time, it will just be handled by ARC.
The compiler isn't able to determine that str6 is the same immutable string as the others. This is only a build time implication that the others all ended up with the same address.
Another interesting thing to note is that you will never see dealloc called on the __NSCFConstantString the compiler is creating for the variables declared in the way you declared them. So the compiler is not only making your code more efficient from a memory stand point, it's also removing all of the memory management code involved in keeping up with these strings.
The first call to NSLog is printing the addresses of the 3 local variables str1, str2 and str3. They are all residing on the stack because they are local, hence the large addresses.
The second NSLog call is printing the addresses of the objects pointed to by str1, str2 and str3, which in this case has been optimised to the same object.
Your local variables are already pointers to NSStrings, not actual NSStrings themselves, so you don't need the address operator &.
Thats interesting that you got the result but makes sense str1, str2, str3 are all immutable and so they will never change and all have the same contents, so instead of getting 3 new NSString you have just got the same one three times. If you change them to NSMutableStrings you will get the result you expected. I played around with this a little more and I found if you turn of ARC and add this
NSLog(#"str1 = %lu , str2 = %lu, str3 = %lu",
[str1 retainCount], [str2 retainCount], [str3 retainCount] );
you get some more interesting stuff, I was expecting to see the value 3, three times to represent the three allocs for the same object but instead you get 18446744073709551615, which makes sense when you also add this
NSLog(#"str1 = %# , str2 = %#, str3 = %#",
[str1 class], [str2 class], [str3 class] );
You will see the class is __NSCFConstantString which is not reference counted its the equivalent to c literal string.
You can get a similar thing with a lot of c compilers where if you define a literal c string like
char * cstr1 = "abc";
char * cstr2 = "abc";
printf( "cstr1 == cstr2 = %s\n", cstr1 == cstr2 ? "true" : "false" );
You can see the compiler has save memory for literals by only having one "abc" and pointing all pointers to the same place.
Thinking about it a little more the init method for NSString may look something like this
- (instancetype)init {
[self release];
return #"";
}
str1 is the memory address of the string
&str1 is the memory address of the pointer to the string
(could be the other way round)

Weird error with NSString: No known class method for selector 'stringWithBytes:length:encoding:'

I am attempting to use scanf to assign a value to an NSString, as per the answers to this question by Omar. This is the code, taken straight from progrmr's answer:
char word[40];
int nChars = scanf("%39s", word); // read up to 39 chars (leave room for NUL)
NSString* word2 = [NSString stringWithBytes:word
length:nChars
encoding:NSUTF8StringEncoding];
However, I'm getting an error on the last line that makes absolutely no sense to me:
No known class method for selector 'stringWithBytes:length:encoding:'
What in the world could be causing this error?
And yes, I do have #import <Foundation/Foundation.h> at the top of the file.
NSString does not have a stringWithBytes:length:encoding: class method, but you can use
NSString* word2 = [[NSString alloc] initWithBytes:word
length:nChars
encoding:NSUTF8StringEncoding];
Note however, that scanf() returns the number of scanned items and
not the number of scanned characters. So nChars will contain 1 and not the string length, so you should set nChars = strlen(word) instead.
A simpler alternative is (as also mentioned in one answer to the linked question)
NSString* word2 = [NSString stringWithUTF8String:word];
NSString does not respond to the selector stringWithBytes:length:encoding:. You probably wanted initWithBytes:length:encoding:.
Story in short: you might want to consider a const char C-string suitable initializer for your NSString object. Also, allocate memory before sending any initializer message to the NSString object. I would expect something like:
char word[40];
int nChars = scanf("%39s", word);
NSString *word2 = [[NSString alloc] initWithCString:word encoding:NSASCIIStringEncoding];
Note that initWithCString per design only supports properly null '\0' terminated 8-bit character arrays. For unterminated bytes arrays you have initWithBytes:length:encoding: instead.
For Unicode characters you could consider initWithCharactersNoCopy:length:freeWhenDone:.

NSString type declaration

I'm studying Objective-C. Can you tell me what is the difference (if any) between these NSString declarations?
NSString *firstString;
firstString = #"First string";
NSString *secondString = [NSString string];
secondString = #"Second string";
The second one creates two strings, and throws the first one away without using it. In this line:
NSString *secondString = [NSString string];
you are creating a new string, which isn't really useful because it's empty, and you're assigning it to secondString. Then you're assigning a different string (#"Second String") to secondString.
There's no need to do this. In either case, you can just write:
NSString *myString = #"MyString";
The syntax #"Some string here" is called a string literal, and it's a shorthand for specifying an NSString with an actual value in your code.
No difference as the end result.
The first string is being declared and then assigned a value via the string literal syntax (you can also do this with NSNumbers as of Xcode 4.4).
The second is being initialised as a string (empty) and then is being assigned another NSString object. In this case there are actually two NSString objects being created, the former - [NSString string] is being overwritten by the latter #"string value"
So, the first one is nil to start with and then has a value. The second had a instantiated NSString object to start and was then overwritten.
In the end both string objects are the same, but obviously you are wasting resources in the second case.

Can't manipulate a string?

NSString *string = #"HELLO";
For some reason, XCode won't auto-complete methods like remove characters or append etc... If that's the case, how can I, say, remove certain characters from my string? Say I want to remove all the L's.
NSString doesn't respond to those methods. NSMutableString does, but you've declared an immutable string variable and assigned to it a string literal. Since an Objective-C #"string literal" is always immutable (an instance of NSString but not NSMutableString), there's no way those messages can be sent to the object you're using.
If you want a mutable string, try:
NSMutableString *mutableString = [[#"HELLO" mutableCopy] autorelease];
That's an immutable string literal.
Here is a great post explaining it in further details:
What's the difference between a string constant and a string literal?
As for your question on how would you change it and remove the Ls:
NSString *hello = #"HELLO";
NSString *subString = [hello stringByReplacingOccurrencesOfString:#"L" withString:#""];
NSLog(#"subString: %#", subString);
That outputs "HEO"
Either that or you can create an NSMutableString by creating a copy of the mutable string like Jonathan mentioned. In both examples, you're copying it into a non-literal string.

stringByAppendingFormat not working

I have an NSString and fail to apply the following statement:
NSString *myString = #"some text";
[myString stringByAppendingFormat:#"some text = %d", 3];
no log or error, the string just doesn't get changed. I already tried with NSString (as documented) and NSMutableString.
any clues most welcome.
I would suggest correcting to (documentation):
NSString *myString = #"some text";
myString = [myString stringByAppendingFormat:#" = %d", 3];
From the docs:
Returns a string made by appending to the receiver a string constructed from a given format string and the following arguments.
It's working, you're just ignoring the return value, which is the string with the appended format. (See the docs.) You can't modify an NSString — to modify an NSMutableString, use -appendFormat: instead.
Of course, in your toy example, you could shorten it to this:
NSString *myString = [NSString stringWithFormat:#"some text = %d", 3];
However, it's likely that you need to append a format string to an existing string created elsewhere. In that case, and particularly if you're appending multiple parts, it's good to think about and balance the pros and cons of using a mutable string or several immutable, autoreleased strings.
Creating strings with #"" always results in immutable strings. If you want to create a new NSMutableString do it as following.
NSMutableString *myString = [NSMutableString stringWithString:#"some text"];
[myString appendFormat:#"some text = %d", 3];
I had a similar warning message while appending a localized string. This is how I resolved it
NSString *msgBody = [msgBody stringByAppendingFormat:#"%#",NSLocalizedString(#"LOCALSTRINGMSG",#"Message Body")];