Calling -retainCount Considered Harmful

The basics: What are the official reasons to not use retainCount?

Autorelease management is the most obvious — you have no way to be sure how many of the references represented by the retainCount are in a local or external (on a secondary thread, or in another thread’s local pool) autorelease pool.

Also, some people have trouble with leaks, and at a higher level reference counting and how autorelease pools work at fundamental levels. They will write a program without (much) regard to proper reference counting, or without learning ref counting properly. This makes their program very difficult to debug, test, and improve — it’s also a very time consuming rectification.

The reason for discouraging its use (at the client level) is twofold:

1) The value may vary for so many reasons. Threading alone is reason enough to never trust it.

2) You still have to implement correct reference counting. retainCount will never save you from imbalanced reference counting.

Is there ever any situation at all when it might be useful?

You could in fact use it in a meaningful way if you wrote your own allocators or reference counting scheme, or if your object lived on one thread and you had access to any and all autorelease pools it could exist in. This also implies you would not share it with any external APIs. The easy way to simulate this is to create a program with one thread, zero autorelease pools, and do your reference counting the ‘normal’ way. It’s unlikely that you’ll ever need to solve this problem/write this program for anything other than “academic” reasons.

As a debugging aid: you could use it to verify that the retain count is not unusually high. If you take this approach, be mindful of the implementation variances (some are cited in this post), and don’t rely on it. Don’t even commit the tests to your SCM repository.

This may be a useful diagnostic in extremely rare circumstances. It can be used to detect:

  • Over-retaining: An allocation with a positive imbalance in retain count would not show up as a leak if the allocation is reachable by your program.

  • An object which is referenced by many other objects: One illustration of this problem is a (mutable) shared resource or collection which operates in a multithreaded context – frequent access or changes to this resource/collection can introduce a significant bottleneck in your program’s execution.

  • Autorelease levels: Autoreleasing, autorelease pools, and retain/autorelease cycles all come with a cost. If you need to minimize or reduce memory use and/or growth, you could use this approach to detect excessive cases.

From commentary with Bavarious (below): a high value may also indicate an invalidated allocation (dealloc’d instance). This is completely an implementation detail, and again, not usable in production code. Messaging this allocation would result in a error when zombies are enabled.

What should be done instead?

If you’re not responsible for returning the memory at self (that is, you did not write an allocator), leave it alone – it is useless.

You have to learn proper reference counting.

For a better understanding of release and autorelease usage, set up some breakpoints and understand how they are used, in what cases, etc. You’ll still have to learn to use reference counting correctly, but this can aid your understanding of why it’s useless.

Even simpler: use Instruments to track allocs and ref counts, then analyze the ref counting and callstacks of several objects in an active program.

Historical/explanatory: Why does Apple provide this method in the NSObject protocol if it’s not intended to be used? Does Apple’s code rely on retainCount for some purpose? If so, why isn’t it hidden away somewhere?

We can assume that it is public for two primary reasons:

1) Reference counting proper in managed environments. It’s fine for the allocators to use retainCount — really. It’s a very simple concept. When -[NSObject release] is called, the ref counter (unless overridden) may be called, and the object can be deallocated if retainCount is 0 (after calling dealloc). This is all fine at the allocator level. Allocators and zones are (largely) abstracted so… this makes the result meaningless for ordinary clients. See commentary with bbum (below) for details on why retainCount cannot be equal to 0 at the client level, object deallocation, deallocation sequences, and more.

2) To make it available to subclassers who want a custom behavior, and because the other reference counting methods are public. It may be handy in a few cases, but it’s typically used for the wrong reasons (e.g. immortal singletons). If you need your own reference counting scheme, then this family may be worth overriding.

For deeper understanding: What are the reasons that an object may have a different retain count than would be assumed from user code? Can you give any examples*** of standard procedures that framework code might use which cause such a difference? Are there any known cases where the retain count is always different than what a new user might expect?

Again, a custom reference counting schemes and immortal objects. NSCFString literals fall into the latter category:

NSLog(@"%qu", [@"MyString" retainCount]); 
// Logs: 1152921504606846975

Anything else you think is worth mentioning about retainCount?

It’s useless as a debugging aid. Learn to use leak and zombie analyses, and use them often — even after you have a handle on reference counting.


Update: bbum has recently posted an article entitled retainCount is useless. The article contains a thorough discussion of why -retainCount isn’t useful in the vast majority of cases.

Leave a Comment