retainCount is useless.

The retainCount method just isn’t very useful as has been indicated in the answers to about a zillion StackOverflow questions.

The documentation has this to say:

Important: This method is typically of no value in debugging memory management issues. Because any number of framework objects may have retained an object in order to hold references to it, while at the same time autorelease pools may be holding any number of deferred releases on an object, it is very unlikely that you can get useful information from this method.

Makes it pretty clear that you shouldn’t be calling retainCount, but it really doesn’t illuminate exactly how useless the method is outside of a very narrow context.

So, let us count the ways:

  1. The absolute retain count of an object may change at any time once an object has been passed through any system API.
  2. Any subclass of any system provided class counts as “through system API”; the retain count may be impacted by implementation details.
  3. The retain count never reflects whether an object is autoreleased.
  4. Autorelease is a per-thread concept whereas retain counts are global; race condition derived hilarity can easily ensue.
  5. The retainCount method can never return 0.
  6. Some classes are implemented as singletons some of the time.
  7. Some classes may internally manipulate their retain count directly (I.e. no swizzle for you!).
  8. While retain/release are effectively thread safe, there is always a race between calling retainCount and having the actual retain count change in some other execution context.

Bottom line: the only time the absolute retainCount can be conclusively used for analytic purposes is if you have the backtrace of every retain and release that contributed to the current retain count’s value (there is another use case, documented at the end). If you have that, then you don’t need the retain count and, fortunately, Instruments is already generally quite adept at producing a per-object inventory of retains and releases for you (see Analyzing Data with the Allocations Instrument“.

In general, you should consider the retain count as a delta. Your code causes the retain count to increase and decrease. You don’t +alloc an object with a retain count of 1. Instead, you +alloc an object with a retain count of +1. If you want that object to go away, you need to do something — release, always and eventually — that causes the retain count to be decremented by 1.

It really is that simple.

Almost.

Concurrency — whether through threading or GCD — throws a bit of a wrench in the works (as it always does). Namely, the retain count should really be considered as a per concurrency context delta. If thread or queue A wants to do something with object X, it should hold a +1 retainCount on X for the duration of said operation. One subtle detail that will bear repeating later; an autorelease‘d object does not contribute to thread safety. That is, if thread or queue A wants to pass X to thread or queue B, there must be an explicit retain in A that is balanced by a release (or synchronization event back to A) in B.

Now, some of the enumerated claims may either seem specious at best or, certainly, not obvious as to exactly how it undermines the validity of the retain count.

  1. The absolute retain count of an object may change at any time once an object has been passed through any system API.
  2. The system is free to retain and release any object at whim and, in fact, it will often do so. An object may be shoved into a collection, it might be cached into a map somewhere by a computed key, or it might be retain‘d long enough to survive enqueueing for execution asynchronously.

    As well, the object may be retain’d then autorelease’d such that the retainCount will look like it is N when it is really, effectively, N-1. If that retain/autorelease happened in a queue somewhere, the number returned may imply a greater # of strong references than truly exist.

  3. Any subclass of any system provided class counts as “through system API”; the retain count may be impacted by implementation details.
  4. This is really the same point as the first, but emphasizes that all bets truly are off once you subclass a system class. As the system’s use of concurrency during drawing grows over time, you can expect that any UI related subclassed classes will likely have their retain counts manipulated across concurrency boundaries potentially frequently.

  5. The retain count never reflects whether an object is autoreleased.
  6. When you call autorelease, not much really happens. The object is neither retain‘d nor release‘s. Instead, it is added to the current per-thread autorelease pool. The release is effectively delayed until the pool is drained or deferred to the next outermost pool if the pool is popped without draining (which can happen due to bugs or exceptions [exceptions are an indication of bugs in and of themselves in Cocoa]).

    Once an object is added to an autorelease pool, there is no way to query whether or not it is in such a state. Thus, whatever retain count is currently returned may not actually reflect the effective retain found at all.

  7. Autorelease is a per-thread concept whereas retain counts are global; race condition derived hilarity can easily ensue.
  8. But it gets worse (at least, worse for the developer making the mistake of thinking retainCount is meaningful).

    The retain count of an object is actually global to all contexts of concurrency. An object has one and only one retain count. While calling retain and release are concurrency safe, there is no way to express call retain and don’t let another execution context increment or decrement it until I’m done pondering the return value.

  9. The retainCount method can never return 0.
  10. When an object’s retain count transitions from 1 to 0, the object is deallocated (usually). As a result, if you retainCount on a non-nil reference, it can never return zero because doing so would require calling a method on a deallocated object which is undefined behavior.

    Or, to put it more bluntly, this can never work (Yes, I had to go there):

    while([bg retainCount]!=0)
    {
      [bg release];
    }
    

    One might claim that if you message nil, you’ll get 0 in return. True; but no code outside of a short circuit in objc_msgSend() is executed. That is, the method isn’t returning zero, the messaging mechanism is short circuiting prior to calling the method.

  11. Some classes are implemented as singletons some of the time.
  12. Consider:

    NSNumber *n = [NSNumber numberWithInt: 4];
    printf("%p rc: %u\n", n, [n retainCount]);
    n = [NSNumber numberWithInt: 4];
    printf("%p rc: %u\n", n, [n retainCount]);
    n = [NSNumber numberWithInt: 42];
    printf("%p rc: %u\n", n, [n retainCount]);
    n = [NSNumber numberWithInt: 42];
    printf("%p rc: %u\n", n, [n retainCount]);
    

    And the output on i386:

    0x115410 rc: 2
    0x115410 rc: 3
    0x115950 rc: 1
    0x115df0 rc: 1
    

    Now consider x86_64:

    0x483 rc: 9223372036854775807
    0x483 rc: 9223372036854775807
    0x2a83 rc: 9223372036854775807
    0x2a83 rc: 9223372036854775807
    

    For this particular class, the behavior is different depending on value and depending on architecture. You could expect potentially even different behavior on an iOS device. Likewise, if you were to run the 64 bit variant on Snow Leopard, it would be different yet again!

    Internally, NSNumber uses singletons for a small subset of values on certain platforms. On x86_64, it uses tagged pointers (Bavarious explains tagged pointers quite well in this blog post. I should update the objc_msgSend() tour to reflect the new messenger in Lion, too) to avoid allocating anything for the object altogether.

    In all cases, relying upon the retain count of an NSNumber will create a dependency on system behavior that will quite likely change in the future, breaking your program rather badly.

    Many other classes within the frameworks have similar optimizations for the common cases. Likewise, because of polymorphism and/or duck typing, it is likely that a system API declared to return an instance of, say, NSString might return an instance of a seemingly unrelated class whose interface is string compatible.

  13. Some classes may internally manipulate their retain count directly (I.e. no swizzle for you!).
  14. It may be tempting to override retain, release and autorelease in existing classes using a swizzling mechanism or the like. Depending on the class, you might find that the retain count of an object is changing more than the # of calls to retain/release/autorelease might otherwise warrant!

    This is because a class might choose to implement a retain count mechanism differently than the one provided by NSObject and, thus, might internally directly manipulate the retain count without going through the various methods.

    Since the retainCount of an object is not an observable property (ewww… boy howdy that’d be ugly if it was), this direct manipulation is perfectly valid. However, it means that you cannot count on being able to externally modify an existing class to observe the retain count.

    Nor should you need to. The system classes are designed such that they should be compatible with the Allocation Instrument’s ability to track reference count changing events, including grabbing a backtrace. If you find a class that isn’t, file a bug, please.

  15. While retain/release are effectively thread safe, there is always a race between calling retainCount and having the actual retain count change in some other execution context.
  16. Mmm… concurrency… where 1+1 can yield 3.

    As was mentioned before, there is no operation via which you can query an object for its retainCount in a fashion that guarantees the retain count won’t change immediately after you retrieve the value, including potentially deallocating the object.

    That is to say, there is no retain/release locking mechanism.

    Thus, as soon as you retrieve the retainCount of an object, that value may be incorrect unless you can absolutely guarantee that the object is not being manipulated by any other execution context. And the only way to guarantee that is to pretty much stick to a direct subclass of NSObject (which, in and of itself, isn’t really technically correct either) and limiting manipulation of said object(s) only to threads/queues where you exactly control every aspect of interaction with said object; i.e. no system APIs for you!

The above doesn’t even consider the inherent design fragility in colluding the reference count of objects with other functionality. The classic “scarce resource recollection” problem of GC very much exists in a manual retain-release realm, for example.


So, when is retainCount useful?

I have encountered a handful of cases where the retain count of an object was used internally as a sort of “mode of use” indicator. That is, if an object’s retainCount is 2 when -release is invoked, the framework will assume, say, that the object is transitioning from “in use” to “available for quick reuse”. Typically, this is a sort of allocation cache or instance cache. Often, this is done because instantiation of the object is fairly expensive and, thus, the goal is to create a pool of available objects that can quickly be reused.

While a valid use, it tends to be fragile and tends to make crashes more difficult to analyze. Namely, one stray release or autorelease and you are likely to have a crash in the caching mechanism. At least with a crash in in NSAutoreleasePool, it is immediately obvious where things went off the rails.

A far less fragile solution is to encapsulate the expensive bits within a second container within the object. That container can be “checked in” and “checked out” by the object as a normal part of allocation/deallocation. This decouples cache magic from retain count.


The best possible solution to all of this is to move to ARC! Automatic Reference Counting, by and large, works incredibly well for a technology that is so new. The compiler and runtime teams have taken a very conservative approach that makes this so. Basically, if the compiler can’t reason through a piece of code to determine with total certainty exactly what the retain/release behavior should be, it generates an error instead.

It works quite well and many of Apple’s newer releases feature big chunks of code that are ARC’d.

If you can’t go ARC yet for whatever reasons, then dealing with manual retain-release is really quite simple and covered well in the documentation. The key is to ignore the retain count as a value and treat it purely as something you increase and decrease; you must balance every increase with a decrease if you want a particular object to be deallocated or over-released in the case of too many decrements.



16 Responses to “retainCount is useless.”

  1. Steve Weller says:

    A corollary of the above would appear to be that any object can be dealloced on any thread. Is this so? And if so, how can it be prevented? There are some objects (UIKit comes to mind) that distinctly do not like being dealloced on a non-main thread.

  2. bbum says:

    @Steve An object will be deallocated on the thread that sends the release that causes the retain count to drop to 0 (or would, anyway, as the retain count is never decremented to zero because that’d be silly). Thus, if you are following the rules of using UIKit objects, then said objects will always be deallocated on the main thread because that is the only place where you should be messaging them from (save for a handful of well documented exceptions that would not lead to the final release).

  3. Ian McCullough says:

    Great article, Bill. You and I have sparred on this topic before, and I’m glad to see so many of the issues covered here with your usual high level of knowledge and clarity. It’ll be good link fodder for SO answers/comments. I know you’ll probably disagree with me that this is generally applicable or useful, but I’ve personally found it useful in the past to override retain/release, (the method bodies @synchronized, of course,) and check -retainCount during -release specifically to answer the question, “is this the release call that would cause me to dealloc?” I realize that this information is not generally useful (since the release call could come from anything, and the ‘corresponding’ retain could have been taken by anything) but it’s helped me in specific, targeted instances in the past.

    Relatedly, one thing I’ve long yearned for is some sort of “annotation” UI in Instruments’ Allocations reference count lists. I’ll routinely find myself printing out these lists, then going through and drawing lines to relate retain/release/autorelease calls I believe to be logically balanced, looking for the “odd man out.” This process is frustrating sometimes, because you want to be able to jump back and forth between stack traces and source views, etc., and simple UI to facilitate doing this in situ (instead of on paper) would be neat. Got any tips for that workflow? I feel like it must be almost a rite of passage for ObjC programmers.

  4. Nat! says:

    Of course the -retainCount concept doesn’t exist in Java, so what good can it be ? :)

    First you say “retainCount is useless. Then you say “The retainCount method just isn’t very useful”. Then you claim “you shouldn’t be calling retainCount”. I get a little panicky, when I read stuff like this, because mucking up a good thing, seems to be the tendency with Objective-C (old guy rant :)). While points 1-8 in general do explain the issues (well) to look out for when using -retainCount, they in no way prove the claim, that the method is useless. Thus my need to reply.

    I don’t get point 5. Isn’t that just a recipe for crashing ? When -retainCount == 1, then bg gets -release/-dealloc and the next -retainCount messages a zombie at best.

    I don’t get point 7. Do you mean -retainCount is not an observable property in terms of KVO ? Or are you saying that classes should be allowed to return a *wrong* retainCount ?

    Point 8 is wrong. The -retainCount of 1 is the key! It means, the object is owned by the current thread (only), so no other thread can access this instance. You may also, depending on your code, be able to claim that you are the sole owner of that object (when you have the right to -release it). These deduced properties about the instance – even of classes, that aren’t your own classes – enable very useful algorithms. All with the help of humble, little -retainCount. This isn’t just academic, I have important code that relies on that.

  5. Mike Lee says:

    Nat!, I’m afraid your “deduced” properties are too clever by half. Retain count does not in any way imply ownership. How could it? It’s global. Every thread sees the same count, with no way of deducing where it came from. All a retain count of 1 implies is that it exists. Anyone can claim to have created it, to own it or behave as such. How is that ownership? Moreover, why do any of this? If your methods are so radical as to have eked out a valid use of retainCount, that should be cause for concern, not celebration.

  6. bbum says:

    Nat!:

    Of course the -retainCount concept doesn’t exist in Java, so what good can it be ?

    Heh; I was an Objective-C programmer long before I was a Java programmer (Java didn’t exist when I started with Objective-C). In porting the original source release of the JDK to NS 3.3, that I didn’t have to map to retain/release/autorelease made it a bit easier (IIRC — that was a long time ago). Java was some fun times; taught me the value of GC in concurrent systems and how important good design and systemic consistency are critical to APIs. ;)

    I don’t get point 5. Isn’t that just a recipe for crashing ? When -retainCount == 1, then bg gets -release/-dealloc and the next -retainCount messages a zombie at best.

    You exactly got it. I’ve run across that code multiple times over the years. It doesn’t work and the crashes baffled the coder. In a few cases, they converted it to test for rC != 1 and moved their bafflement to a different subsystem within their app.

    I don’t get point 7. Do you mean -retainCount is not an observable property in terms of KVO ? Or are you saying that classes should be allowed to return a *wrong* retainCount ?

    There is not necessarily a 1 to 1 correspondence between the retain count and calls to retain/release. 7 was mostly focused on advanced debugging techniques. More than once, myself included, I’ve seen developers swizzle in custom implementations of retain/release/autorelease to track various events. This mostly works and can be quite useful, but it typically falls apart pretty fast when attempted against Foundation and below classes; CF bridging and class clusters being the primary causes of confusion. This point is a bit orthogonal to the “retain count is bad” meme, but related in that developers will often muck with the retainCount in such code.

    For NSString, you can subclass NSString, encapsulate the real NSString instance, forward the primitive methods, and then muck with retain/release/autorelease to your heart's content (the custom subclass forces F/CF to take the "slow path" in maintaining the retain count).

    Mike addressed your point regarding point 8...

  7. Danyal says:

    You had me at System API.

  8. Jason Brennan says:

    @bbum: Why is -retainCount public API? My only guess is to allow singletons to override it and return a very large number so it never gets deallocated, but that’s the best I can come up with. Why is this API?

  9. jaminguy says:

    @bbum why not just deprecate retainCount until removing it as a public method all together?

  10. rond says:

    @bbum: Thanks for the great info, as always. Very helpful.

    Upon reflection, though, I have to concur with a couple of the other comments, namely … if this property is not quite what it appears to be (and/or is not useful for what it appears to be), and thus is just an ongoing source of confusion (as evidenced by “a zillion StackOverflow questions”), and in fact is “useless” — then why expose it at all?

    If the answer is to be able to override it to return UINT_MAX for the purpose of Singleton classes … then why not deprecate it, and implement some kind of “-setRetainCountToMax(BOOL)” method instead?

  11. Steve Weller says:

    Here’s a simple project that includes an example of a UIKit object being deallocated on a background thread, despite meeting all the requirements for not messaging UIKit objects on other threads.

    http://dl.dropbox.com/u/1775688/Test3.zip

    Tap a row. Wait until the text says Done, then tab Back. Repeat a few times until it crashes.

    The outer (background) block in DetailController invisibly retains self so that t can pass it to the inner block. Since the outer block runs in the background past the time that the inner block and its dependent code runs, the last release is from the background thread, calling dealloc on that thread. This pattern is likely very common for anyone using GCD, and, I suspect, for networking code implemented by the system, but I believe few suspect that their deallocs can ever be called on background threads.

    That this can occur at all is another very good reason for avoiding side-effects in dealloc. But if side effects are necessary, what is the best ay to handle this situation?

  12. Steve Weller says:

    Forgot to mention: run that test app in the iPhone simulator, or adjust the loop constants appropriately for the speed of a real device.

  13. ppcaric says:

    I was at the last iOS developer convention in Rome and the single session that really impressed me where the one about ARC. Is there any reason why you should be keeping on dealing with all the alloc/retain/release headache generator stuff, when your compiler can do it for you and even improve the efficiency of the compiled code? I’ve instantly abandoned the old compiler and converted all my code to ARC (which was mostly done by Xcode utility BTW). Am I missing something?

  14. bbum says:

    @ppcaric Unless you have a need for backwards compatibility or you have a codebase that does lots and lots of gnarly CF based stuff (ewww) then, yes, use ARC.

  15. PPC says:

    Backward compatibility should be no issue. You don’t need iOS 5 to use ARC based code. It’s transparent to the user: you need llvm as compiler, but the compiled code is executable on older iOS versions too. About CF, that’s something I hadn’t thought about in fact :\
    Are there problems if you use ARC and CF?

  16. bbum says:

    First, while ARC is mostly compatible with older versions of the OS, the weak references subfeature is not; you can’t use zeroing weak on older OSes.

    Secondly, the border between CF and ARC is brittle. You have to continue managing CF references using CFRetain/CFRelease and casting-to-NS-to-use-retain/release/autorelease is verboten. There are a series of compiler directives and/or function calls that make memory management intent declarative that have to be added at every site where there is an NS->CF or CF->NS cast. The migration tool handles many of these relatively automatic, but it can be very tedious.

    Bottom line: Don’t use CF unless you absolutely have to (and “because of performance” is pretty much never a good reason because there is no performance gain to be had outside of a few specific patterns).

Leave a Reply

Line and paragraph breaks automatic.
XHTML allowed: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>