Clarification on NSString methods - ios

I would like to get a clarification/difference on the NSString declaration. Consider the following codes:
NSString *str = #"string";
NSString *str = [NSString stringWithFormat:#"string"];
NSString *str = [[NSString alloc] initWithString:#"string"];
Can anyone help me to understand the difference between the above three type of string declaration? Does the difference come in terms of memory or will there be any other reasons? I have gone through various posts to understand the difference, but I couldn't understand the exact difference.
Sree

Edit (thanks for the comments):
Using ARC, the first statement is used by the compiler to create a string that is accessible during the lifetime of the app and never deallocated.
The last two statements produce the same kind of string.
Using manual memory management, the second statement produces an autoreleased string.
The last produces a retained string. This means, when using the last statement, you would have to add a release later in the code.

for: NSString *str = #"string"; used when you use as a static string .
for Exmple.
int abc=5;
for: NSString *str = [NSString stringWithFormat:#"%d",abc]; used when you convert your integer or float into string.
for: NSString *str = [[NSString alloc] initWithString:#"string"]; used when above same same reason but only difference is you alloc string and then passes static string.
but in ARC no need to alloc string.

as I remember in this case
NSString *str = #"string";
the memory for the string will be allocated once and will be reused for all same strings in whole application. Something like global constant.
https://en.wikipedia.org/wiki/String_literal
There is the proof: "Objective-C string constant is created at compile time and exists throughout your program’s execution. The compiler makes such object constants unique on a per-module basis, and they’re never deallocated"
https://developer.apple.com/library/ios/documentation/Cocoa/Conceptual/Strings/Articles/CreatingStrings.html
The others 2 is the same, also you can formate string with stringWithFormat method: https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/Strings/Articles/FormatStrings.html#//apple_ref/doc/uid/20000943

NSString literal
#"string"
This will make the compiler emit a statically allocated NSString instance. The object itself will never be deallocated (release is a no-op).
Unique instance from format
[NSString stringWithFormat:#"string"]
Here the string is created from parsing and transforming the format string. This involves runtime overhead and returns an autoreleased instance.
Initialize by copying a literal
[[NSString alloc] initWithString:#"string"]
This will (1) create the literal, (2) allocate a default string, (3) throw away the default string, (4) call copy on the literal, and (5) return the result (again, the literal, because copy just returns it). The logic retain count is +1 (automatically handled by ARC).

Related

ObjectiveC: Strange behavior when using NSString and __weak with ARC

First code and output:
NSString *text = #"Sunny";
__weak NSString *string0 = text.lowercaseString;
__weak NSString *string1;
string1 = text.lowercaseString;
NSLog(#"%#, %#", string0, string1);
Output:
(null), sunny
But after I move the declaration of string1 above the text, output is different. Here is the code:
__weak NSString *string1;
NSString *text = #"Sunny";
__weak NSString *string0 = text.lowercaseString;
string1 = text.lowercaseString;
NSLog(#"%#, %#", string0, string1);
Output:
sunny, sunny
I am very confused with the different output:
Why string0 and string1 is different in the first case?
Why the output of second case is different with the first?
Trying to figure out exactly when an object is released or a weak reference nulled can be challenging, and often doesn't really help understanding. Here are some reasons why you can see different results than you expect:
NSString: It is best never to use this type when doing these kind of investigations. String literals are immortal, they are not collected, and you may have a string literal even when you don't expect one.
The auto-release pool: The auto-release pool is really a hangover from the pre-ARC days, but it still exists and many methods return auto-released objects. What this means is many objects will live longer than you might expect, but not too long. However ARC has tricks and can remove objects from the auto-release pool early, so you might first think the object will live longer and then it doesn't...
weak references: After the first two bullets you should guess that as you might have no real idea when an object gets released, if at all, then you might have no real idea when a weak reference gets nulled. Just think "soon enough".
Optimisation: There is some leeway in optimisations the compiler can do which, while retaining the correct semantics of your program, may alter the lifetime of objects.
If you do want to run these kind of investigations then you will probably get further if (a) use your own class types, not anything from the libraries and (b) use #autoreleasepool { ... } blocks to limit the lifetimes of auto-released objects.
As an example, when I ran your code on the compiler I was using I did not get a (null), however changing the first assignment to string0 = text.lowercaseString.mutableCopy did produce one... Figuring out why is left as an exercise...
Have an inquiring mind and explore, that is good, but be prepared for the non-obvious!
HTH

Value stored to NSString during its initialization is never read

In my iOS app I have following code:
case SASpeechSubCase03:
{
SAActivity currentActivity = self.mediator.selectedActivity;
NSString *sActivity = NSLocalizedString(#"activity", #"activity");
NSString *sActivity2 = NSLocalizedString(#"another activity", #"another activity");
if(currentActivity == SAActivityWalk)
{
sActivity = NSLocalizedString(#"walk", #"walk");
sActivity2 = NSLocalizedString(#"walking", #"walking");
}
else
{
sActivity = NSLocalizedString(#"run", #"run");
sActivity2 = NSLocalizedString(#"jogging", #"jogging");
}
return [NSString stringWithFormat:speech.text, sActivity, sActivity2];
break;
}
When I run bots on it, it gave me following warning:
Bot Issue: analyzerWarning. Dead store.
Issue: Value stored to 'sActivity' during its initialization is never read.
File: SAAnnouncementService.m.
Integration Number: 42.
Description: Value stored to 'sActivity' during its initialization is never read.
Bot Issue: analyzerWarning. Dead store.
Issue: Value stored to 'sActivity2' during its initialization is never read.
File: SAAnnouncementService.m.
Integration Number: 42.
Description: Value stored to 'sActivity2' during its initialization is never read.
Can someone tell what the problem might be here?
Any kind of help is highly appreciated!
The problem is that you initialized the variables and then directly started the if-else blocks, without using, i.e. reading, the initial values.
When execution gets to the if-else blocks, it will definitely be assigned a new value, no matter what value it was before.
With the following line :
NSString *sActivity = NSLocalizedString(#"activity", #"activity");
NSString *sActivity2 = NSLocalizedString(#"another activity", #"another activity");
You are assigning string values to the sActivity and sActivity2 objects.
Then, these two values are modified in either if or else statement.
But, as the static analyzer mentions, the initial values of these objects (#"activity" and #"another activity") were never read before the second assignment (in if / else statement).
To avoid this warning you can replace the two lines above, by :
NSString *sActivity = nil;
NSString *sActivity2 = nil;
Hope that helps ;)
When you get a warning, the compiler tells you "what you are doing here looks like nonsense, and is most likely not what you want".
Look at these two statements:
NSString *sActivity = NSLocalizedString(#"activity", #"activity");
NSString *sActivity2 = NSLocalizedString(#"another activity", #"another activity");
Does the assignment serve any purpose? It doesn't look like it. So the compiler thinks "either the guy made a rather expensive call that is completely pointless, or he actually intended to use the result of NSLocalizedString but stored it in the wrong place. "
Since the compiler assumes that people don't do pointless things, it assumes that there is a bug in your code and tells you about it. It's the kind of thing where a human reviewing your code would stop and ask you what you were intending to do there.
In your codes, sActivity would be set to either walk or run within IF/ELSE, so that the value set for sActivity this line
NSString *sActivity = NSLocalizedString(#"activity", #"activity");
would never be read. It might not cause error but analyzer reminded you about this superfluous initialization. Try NSString *sActivity=nil;, see if the warning could be turned down.
You are not using sActivity in if-else blocks, you are simply assigning it values based on decision, So either take it nil string like
sActivity = nil;
or like
NSString *sActivity;
to remove waring .

Filename string is used without and with # sign

The iOS SpeakHere example code has a pair of methods in Class SpeakHereController, stopRecord and record that respectively save and initialize a file for saving the recording. The two methods handle the filename string slightly differently as you can see in the following two lines of code in those methods.
recordFilePath = (CFStringRef)[NSTemporaryDirectory() stringByAppendingPathComponent: #"recordedFile.caf"];
recorder->StartRecord(CFSTR("recordedFile.caf"));
The string "recordedFile.caf" occurs once preceded by an # sign and once without. I am planning on using the following NSString constuct to produce filename, but I don't know how to use the result correctly in the two places mentioned in this paragraph. So my question is how to use the constructed string filename in those lines?
#property int counter;
NSString *filename = [[NSString alloc] initWithFormat:#"recordedFile%d.caf",self.counter];
try
recorder->StartRecord(CFSTR([filename UTF8String]));
The difference is between a C API for string objects (CFStringRef in Core Foundation) vs. an Objective-C API for string objects (NSString in Cocoa).
The compiler knows that #"..." is an Objective-C string literal and constructs a static instance of (a private subclass of) NSString.
The compiler doesn't exactly have a similar native knowledge of CFString literals. Instead, Apple's headers define the CFSTR() preprocessor macro to wrap a C-style string in a Core Foundation string. The argument passed to the macro must be a C string literal (e.g. "foo"). The syntax in the other answer which passes a run-time expression returning a non-literal C string pointer not only isn't correct, I don't believe it can compile. Depending on the compiler, CFSTR() may produce a true compile-time CFString object rather than being a run-time expression.
So: #"recordedFile.caf" is an NSString literal, CFSTR("recordedFile.caf") is a C string literal turned into a CFStringRef. You should just think of it as a CFString literal.
Happily, Apple designed Core Foundation and Cocoa so that NSString and CFString are toll-free bridged. They are, at a certain level of abstraction, the same sort of object. You can just type-cast between the two types freely. With ARC, such a type cast requires a bridge that lets ARC know about the memory management of the change.
So, you can do:
CFStringRef cfstring = CFSTR("recordedFile.caf");
NSString* nsstring = (__bridge NSString*)cfstring;
Or:
NSString* nsstring = #"recordedFile.caf";
CFStringRef cfstring = (__bridge CFStringRef)nsstring;
For your particular case:
recorder->StartRecord((__bridge CFStringRef)filename);

iOS Conversion from a dictionary to a NSString

I have a NSMutableDictionary holding EXIF metadata from a picture.
An example:
const CFStringRef kCGImagePropertyExifExposureTime;
Instead of accessing every key individually, I just want write the whole dictionary content into a label.
When I want to write this data into the console I would just use:
NSLog(#"EXIF Dic Properties: %#",EXIFDictionary );
That works fine, but if I use:
NSString *EXIFString = [NSString stringWithFormat:(#"EXIF Properties: %#", EXIFDictionary)];
I get warnings that the result is not a string literally and if I try to use that string to set my label.text, the program crashes.
Any idea where my error is?
[NSString stringWithFormat:(#"EXIF Properties: %#", EXIFDictionary)] is not, as you may think, a method with two arguments. It's a method with one argument. That one argument is (#"EXIF Properties: %#", EXIFDictionary), which uses the comma operator and ends up returning EXIFDictionary. So in essence you have
[NSString stringWithFormat:EXIFDictionary]
which is obviously wrong. This is also why you're getting a warning. That warning tells you that the format argument is not a string literal, because using variables as format strings is a common source of bugs. But more importantly here, that argument isn't even a string at all, and so it crashes.
Remove the parentheses and everything will be fine. That will look like
[NSString stringWithFormat:#"EXIF Properties: %#", EXIFDictionary];
I get warnings that the result is not a string literally
Nah. You get a warning saying that the format string of stringWithFormat: is not a string literal. That's because you don't know how the comma operator (and a variadic function) works (that's why one should master the C language before trying to make an iOS app). Basically what you have here:
[NSString stringWithFormat:(#"EXIF Properties: %#", EXIFDictionary)]
is, due the behavior of the comma operator, is equivalent to
[NSString stringWithFormat:EXIFDictionary]
which is obviously wrong. Omit the parentheses, and it will be fine:
[NSString stringWithFormat:#"EXIF Properties: %#", EXIFDictionary]
You don't want those parentheses:
NSString *EXIFString = [NSString stringWithFormat:#"EXIF Properties: %#", EXIFDictionary];

why not EXC_BAD_ACCESS?

I've written the following code:
NSString *string = [[NSString alloc] initWithFormat:#"test"];
[string release];
NSLog(#"string lenght = %d", [string length]);
//Why I don't get EXC_BAD_ACCESS at this point?
I should, it should be released. The retainCount should be 0 after last release, so why is it not?
P.S.
I am using latest XCode.
Update:
NSString *string = [[NSString alloc] initWithFormat:#"test"];
NSLog(#"retainCount before = %d", [string retainCount]);// => 1
[string release];
NSLog(#"retainCount after = %d", [string retainCount]);// => 1 Why!?
In this case, the frameworks are likely returning the literal #"test" from NSString *string = [[NSString alloc] initWithFormat:#"test"];. That is, it determines the literal may be reused, and reuses it in this context. After all, the input matches the output.
However, you should not rely on these internal optimizations in your programs -- just stick with the reference counting rules and well-defined behavior.
Update
David's comment caused me to look into this. On the system I tested, NSString *string = [[NSString alloc] initWithFormat:#"test"]; returns a new object. Your program messages an object which should have been released, and is not eligible for the immortal string status.
Your program still falls into undefined territory, and happens to appear to give the correct results in some cases only as an artifact of implementation details -- or just purely coincidence. As David pointed out, adding 'stuff' between the release and the log can cause string to really be destroyed and potentially reused. If you really want to know why this all works, you could read the objc runtime sources or crawl through the runtime's assembly as it executes. Some of it may have an explanation (runtime implementation details), and some of it is purely coincidence.
Doing things to a released object is an undefined behavior. Meaning - sometimes you get away with it, sometimes it crashes, sometimes it crashes a minute later in a completely different spot, sometimes a variable ten files away gets mysteriously modified.
To catch those issues, use the NSZombie technique. Look it up. That, and some coding discipline.
This time, you got away because the freed up memory hasn't been overwritten by anything yet. The memory that string points at still contains the bytes of a string object with the right length. Some time later, something else will be there, or the memory address won't be valid anymore. And there's no telling when this happens.
Sending messages to nil objects is, however, legitimate. That's a defined behavior in Objective C, in fact - nothing happens, 0 or nil is returned.
Update:
Ok. I'm tired and didn't read your question carefully enough.
The reason you are not crashing is pure luck. At first I though that you were using initWithString: in which case all the answers (including my original one (below)) about string literals would be valid.
What I mean by "pure luck"
The reason this works is just that the object is released but your pointer still points to where it used to be and the memory is not overwritten before you read it again. So when you access the variable you read from the untouched memory which means that you get a valid object back. Doing the above is VERY dangerous and will eventually cause a crash in the future!
If you start creating more object in between the release and the log then there is a chance that one of them will use the same memory as your string had and then you would crash when trying to read the old memory.
It is even so fragile that calling log twice in a row will cause a crash.
Original answer:
String literals never get released!
Take a look at my answer for this question for a description of why this is.
This answer also has a good explanation.
One possible explanation: You're superfluously dynamically allocating a string instead of just using the constant. Probably Cocoa already knows that's just a waste of memory (if you're not creating a mutable string), so it maybe releases the allocated object and returns the constant string instead. And on a constant string, release and retain have no effect.
To prove this, it's worth comparing the returned pointer to the constant string itself:
int main()
{
NSString *s = #"Hello World!";
NSString *t = [[NSString alloc] initWithFormat:s];
if (s == t)
NSLog(#"Strings are the same");
else
NSLog(#"Not the same; another instance was allocated");
return 0;
}

Resources