NSString initWithString : retain count problem

Today, I realized that initWithString method of NSString behaved differently.

NSString *myString = [[NSString alloc] initWithString:@"my string"];
NSLog( @"retain count = %u", [myString retainCount]; // 1 or 0xFFFFFFFF?

I’m pretty sure that it was 1 before. However, with current version of Xcode and its tool sets, it returns 0xFFFFFFFF.
However, if a string object is created with initWithFormat:, its retainCount is surely 1.

On tweeter, ChangKi Kim, aka MacCrazy, told me that it was changed due to optimization done by Apple, and it treats such immutable string like string literal like :

NSString *myString = @"my string";

The new behaviour is against Apple’s own object ownership rule. Also, initWithString: is definitely for creating an object in a heap not a stack. If a programmer wants to create one in stack, he can always use the string literal.

Why did Apple people break their rule and didn’t write any explanation about it?

When people like me teach Objective-C to others and if this kind of untold change comes up suddenly, students will think that the tutor is not good at Objective-C. Especially it is true here in U.S. Even when interviewed, I was surprised that they didn’t consider long experience and just want to hear “memorized” right answer when you know a lot more than them and you spend your time to figure out in what context they ask questions.
When people who have less variety of knowledge think about a given problem, things can look clear. However, people who have broader knowledge confronted that same question, he first needs to figure out in what context the question is asked. It is like to say that infinity + 1 equals infinity, while a chaos mathematician will answer infinity + 1 is bigger by 1 than infinity. Then the one who doesn’t know the contemporary mathematics thinks that the chaos mathematician is wrong or doesn’t know mathematics.

Anyway, Apple people should not deviate from the original and agreed-upon semantics.

11 responses to this post.

  1. It’s a string literal. Its memory is in the binary itself, and it’s statically allocated. The concept of “releasing it” doesn’t make sense, as that memory can never be reclaimed.

    It’s right there in the documentation at http://developer.apple.com/mac/library/documentation/Cocoa/Reference/Foundation/Protocols/NSObject_Protocol/Reference/NSObject.html#//apple_ref/occ/intfm/NSObject/retainCount :
    “For objects that never get released (that is, their release method does nothing), this method should return UINT_MAX, as defined in ”

    (A common usage of UINT_MAX retainCount apart from compiler literals are singletons.)

    This does not go against Cocoa’s ownership rules. You still manage the object identically to before: get a retain count (implicitly from its alloc, or explicitly with retain if you didn’t create it with alloc or copy) if you plan to hold on to it, and release it when you’re done with it. You should never look at -[retainCount] manually; that information is an implementation detail. In this case, the implementation detail is that -[retain] and -[release] do nothing.

    Reply

  2. … and it’s quite common for -[init…] methods to do optimizations and return other objects from -[init…] than was created with alloc. All class clusters do this, the color classes often do this, and so on. It’s an entirely reasonable optimization to have immutable instances created with ‘initWithString’ with the argument of a literal to return the literal itself and immediately discarding the stack object.

    Reply

    • Posted by jongampark on July 6, 2010 at 8:09 AM

      Yes. You are right. I understand your reasoning.
      However, problem is that there is a way to use a literal string already.

      NSString *myLiteralString = @”This”;

      However, sometimes you will want to create things dynamically but unfortunately it is no different from literal strings.

      NSString *myDynamicString = [[NSString alloc] initWithString:@”This”];

      This means that programmers have choices. If he really want to have a string in a heap, he can use initWithString:@”..”, while if he needs a string literal in a stack, he can use the first form. And this has be true until recently.

      We have used retainCount to figure out if things were kept in some other container in what way.
      For example, in the early days of Cocoa, it was not clear some collection classes will shallow copy an object or deep copy.
      So, it was one way of figuring out how to keep retain count when you put an object into an instance of a collection class.
      If you don’t use retainCount often, the optimization can be handy and reasonable. However, for people like me, whom I believe there are many, it is a kind of weird change in Cocoa. “We know that we can always use explicit string literal if we want. Why do we want that “static” behaviour when the syntax is for dynamic creation?” is the reason behind my claim.

      Reply

      • “Also, initWithString: is definitely for creating an object in a heap not a stack. If a programmer wants to create one in stack, he can always use the string literal.”

        First off, the string literal isn’t on the stack, it’s in the DATA segment.

        Secondly, there is no such rule. All Objective-C objects are conceptually on the heap, and you should code as if they are on the heap. This goes for string literals and alloc created objects alike.

        “However, sometimes you will want to create things dynamically but unfortunately it is no different from literal strings.”

        Why would you want to do that? What difference does it make? No matter if the object is a literal or heap object, you must follow the same basic memory management patterns.

        Collection classes always do shallow copies in Cocoa. Also, even if the documentation didn’t explicitly state this ( http://developer.apple.com/iphone/library/documentation/CoreFoundation/Conceptual/CFMemoryMgmt/Concepts/CopyFunctions.html ), looking at the retain count is a bad way to discern whether the collection did a shallow or a deep copy; it would be easier to figure out by breaking in -[Foo copy] and -[Foo retain], insert a Foo into the collection, and see which one was called.

        ““We know that we can always use explicit string literal if we want. Why do we want that “static” behaviour when the syntax is for dynamic creation?” is the reason behind my claim.”

        And my rebuttal is that there is no static behavior in Cocoa, except for implementation details that you shouldn’t care about.

        Reply

        • Posted by jongampark on July 31, 2010 at 5:40 PM

          Thanks for commenting, Mr. nevyn. I believe avery will know about this issue more well by now.
          ( Wow.. you must keep checking my blog! :) I’m sorry that I have not updated my blog recently. I’m so busy with my work at where I belong. )

          By the way, I think your idea “there is no static behaviour in Cocoa” is too much.
          I think what you are saying and what I am saying is basically same thing, but just with different point of view.

          Reply

  3. Posted by avery on July 30, 2010 at 6:32 PM

    I’m quite new to iPhone development, so I’m still learning the basics.

    I got some unexpected results when using initWithString, which I think may be due to the change that you are describing.

    Would this mean that Apple’s documentation (the first example at http://developer.apple.com/iphone/library/documentation/Cocoa/Conceptual/MemoryMgmt/Articles/mmPractical.html#//apple_ref/doc/uid/TP40004447-SW3) is now wrong?

    Reply

    • Posted by jongampark on July 30, 2010 at 8:39 PM

      Please c.f. nevyn’s comment.
      In general, Cocoa/Objective-C objects still abide by the rule.
      However, the case mentioned in the post is a special case for optimization. Although I don’t agree with that kind of optimization, and I think it is their own break of the rule, in general you should keep the rule.

      Reply

    • No, the example is still right. It just so happens that ‘release’ is a nop in this particular implementation. In iOS5, maybe it’s no longer a nop. You can’t make the assumption that it’s a nop, so just keep following the rules and everything should be all right.

      If things stopped working because of the change above, that means that you were previously not following the rules. Run your app through the Clang Static Analyzer to try to locate your memory management mistake. Also, always follow the memory management rules in the guide you just linked.

      Reply

  4. Posted by jongampark on August 9, 2010 at 6:17 PM

    Here is a very nice explanation and guess about why Apple decided to optimize the initWithString:@””.
    http://kocoa.selfip.com/xe/cocoa/1087

    objcguy explained it very nicely and he supports Mr. Nevyn’s idea.

    I’m sorry that it is written in Korean, though.

    Reply

  5. Posted by petronelas00 on January 30, 2011 at 7:20 AM

    There’s something not quite right with initWithString. Here’s the proof: If I have the code snippet:

    NSString* aString=@”Test”;
    NSString* res;

    //with NSString’s (1) and (2) below do the same thing… why?!

    res=[[NSString alloc] initWithString:aString];//(1)
    //res = aString;//(2)
    NSLog(@”aString=%p res=%p”,aString,res);

    Why aString and res have the same address in memory (as printing with %p shows) as if I’d be assigning aString to res, directly? Shouldn’t res have its own address in memory?

    If optimization issues are at fault, then initWithString should be renamed, otherwise it seems to me there’s a breach of conformity in the interface of initWithString…Am I wrong?

    Thank you.

    Reply

    • Posted by jongampark on January 30, 2011 at 10:02 AM

      Hello, petronelas00,
      Did you read nevyn’s comment? If you didn’t yet, please read his comments. Things are nicely explained. Because I also raised my own concern, it would be helpful to figure out.

      I believe, from your example, the GCC optimizes those intelligently. At least it traced that “res” was initialized with a NSString literal in a stack.

      However, if you try “initWithFormat:”, it will result in the way you expected.

      Reply

Leave a Reply

Please log in using one of these methods to post your comment:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: