What Kind of Leaks Does Automatic Reference Counting in Objective-C Not Prevent or Minimize

What kind of leaks does automatic reference counting in Objective-C not prevent or minimize?

The primary memory-related problem you'll still need to be aware of is retain cycles. This occurs when one object has a strong pointer to another, but the target object has a strong pointer back to the original. Even when all other references to these objects are removed, they still will hold on to one another and will not be released. This can also happen indirectly, by a chain of objects that might have the last one in the chain referring back to an earlier object.

It is for this reason that the __unsafe_unretained and __weak ownership qualifiers exist. The former will not retain any object it points to, but leaves open the possibility of that object going away and it pointing to bad memory, whereas the latter doesn't retain the object and automatically sets itself to nil when its target is deallocated. Of the two, __weak is generally preferred on platforms that support it.

You would use these qualifiers for things like delegates, where you don't want the object to retain its delegate and potentially lead to a cycle.

Another couple of significant memory-related concerns are the handling of Core Foundation objects and memory allocated using malloc() for types like char*. ARC does not manage these types, only Objective-C objects, so you'll still need to deal with them yourself. Core Foundation types can be particularly tricky, because sometimes they need to be bridged across to matching Objective-C objects, and vice versa. This means that control needs to be transferred back and forth from ARC when bridging between CF types and Objective-C. Some keywords related to this bridging have been added, and Mike Ash has a great description of various bridging cases in his lengthy ARC writeup.

In addition to this, there are several other less frequent, but still potentially problematic cases, which the published specification goes into in detail.

Much of the new behavior, based on keeping objects around as long as there is a strong pointer to them, is very similar to garbage collection on the Mac. However, the technical underpinnings are very different. Rather than having a garbage collector process that runs at regular intervals to clean up objects no longer being pointed to, this style of memory management relies on the rigid retain / release rules we all need to obey in Objective-C.

ARC simply takes the repetitive memory management tasks we've had to do for years and offloads them to the compiler so we never have to worry about them again. This way, you don't have the halting problems or sawtooth memory profiles experienced on garbage collected platforms. I've experienced both of these in my garbage collected Mac applications, and am eager to see how they behave under ARC.

For more on garbage collection vs. ARC, see this very interesting response by Chris Lattner on the Objective-C mailing list, where he lists many advantages of ARC over Objective-C 2.0 garbage collection. I've run into several of the GC issues he describes.

How does the new automatic reference counting mechanism work?

Every new developer who comes to Objective-C has to learn the rigid rules of when to retain, release, and autorelease objects. These rules even specify naming conventions that imply the retain count of objects returned from methods. Memory management in Objective-C becomes second nature once you take these rules to heart and apply them consistently, but even the most experienced Cocoa developers slip up from time to time.

With the Clang Static Analyzer, the LLVM developers realized that these rules were reliable enough that they could build a tool to point out memory leaks and overreleases within the paths that your code takes.

Automatic reference counting (ARC) is the next logical step. If the compiler can recognize where you should be retaining and releasing objects, why not have it insert that code for you? Rigid, repetitive tasks are what compilers and their brethren are great at. Humans forget things and make mistakes, but computers are much more consistent.

However, this doesn't completely free you from worrying about memory management on these platforms. I describe the primary issue to watch out for (retain cycles) in my answer here, which may require a little thought on your part to mark weak pointers. However, that's minor when compared to what you're gaining in ARC.

When compared to manual memory management and garbage collection, ARC gives you the best of both worlds by cutting out the need to write retain / release code, yet not having the halting and sawtooth memory profiles seen in a garbage collected environment. About the only advantages garbage collection has over this are its ability to deal with retain cycles and the fact that atomic property assignments are inexpensive (as discussed here). I know I'm replacing all of my existing Mac GC code with ARC implementations.

As to whether this could be extended to other languages, it seems geared around the reference counting system in Objective-C. It might be difficult to apply this to Java or other languages, but I don't know enough about the low-level compiler details to make a definitive statement there. Given that Apple is the one pushing this effort in LLVM, Objective-C will come first unless another party commits significant resources of their own to this.

The unveiling of this shocked developers at WWDC, so people weren't aware that something like this could be done. It may appear on other platforms over time, but for now it's exclusive to LLVM and Objective-C.

To use Automatic Reference Counting or not to use

ARC does a far better job in memory handling then most humans will. Only in certain cases the use of ARC is better turned off, but I doubt many of us developers will ever need too.

Also see :
To ARC or not to ARC? What are the pros and cons?

Does automatic reference counting incur a cost at run-time

ARC isn't free - it will increase and decrease reference counts, and dealloc objects when the reference count is 0.

Compared to manual retain / release, ARC will be correct, it may call retain / release less often because it often knows when a retain / release pair cancels each other out, and the retain / release is faster because it doesn't call Objective-C method calls (in Objective-C, you can override retain / release; with ARC you can't).

Impact of Automatic Reference Counting (ARC) on Memory Leaks

ARC will help you eliminate certain types of leaks, because you won't forget to release or autorelease single objects. For example, this type of error becomes impossible:

myLabel.text = [[NSString alloc] initWithFormat:@"%d", 17];
// oops, just leaked that NSString!

However, ARC will not eliminate leaks caused by retain cycles. It's still up to you to eliminate retain cycles, either by using weak references or by manually breaking the cycles before they become leaked. For example, as we start to use blocks more, block/self retain cycles become much more common. The Transitioning to ARC Release Notes discuss how to avoid these cycles using weak references.

What do I need to know about Automatic Reference Counting?

Naming Conventions. Fix every static analyzer issue and make sure your naming is clear and matches the conventions - this now serves as communication to the compiler. Now, this really is not necessary, but it is good to remove all the compiler and checker warnings, test, ensure your are happy with the naming, then convert. That also means increasing your warning levels and fixing the issues.

Avoid Attributes Details here: Deep copy of dictionaries gives Analyze error in Xcode 4.2

The examples in "Avoid Attributes" demonstrate the importance of type safety, descriptive method names, and ensuring you include what you use in a translation and use strict selector matching. opt: -Wstrict-selector-match. Type safety will also come up when you perform unsafe conversions -- chances are, you will need to introduce type safety via special casting in some parts of your program.

Dangling References to Unmanaged types. Seen here: Assigning an existing CGColor to a CGColor property works in iOS Simulator, not iOS device. Why?

[ARC] does not provide a cycle collector; users must explicitly manage lifetime instead.

This refers to strong cyclic references (e.g. codependent objects). These would exist in your old program. Instruments can help you detect them.

After you have concluded the conversion, be prepared to test several OS versions.

Good Luck!


Extras:

What are the advantages and disadvantages of using ARC?

How does Apple's Objective-C runtime do multithreaded reference counting without degraded performance?

There is overhead and it can be significant in rare cases (like, for example, micro-benchmarks ;), regardless of the optimizations that are in place (of which, there are many). The normal case, though, is optimized for un-contended manipulation of the reference count for the object.

So the question is, if reference counting is so lousy for threading, how does Objective-C do it?

There are multiple locks in play and, effectively, a retain/release on any given object selects a random lock (but always the same lock) for that object. Thus, reducing lock contention while not requiring one lock per object.

(And what Catfish_man said; some classes will implement their own reference counting scheme to use class-specific locking primitives to avoid contention and/or optimize for their specific needs.)

The implementation details are more complex.

Is Objectice-C's reference counting actually technically unsafe with threads?

Nope -- it is safe in regards to threads.

In reality, typical code will call retain and release quite infrequently, compared to other operations. Thus, even if there were significant overhead on those code paths, it would be amortized across all the other operations in the app (where, say, pushing pixels to the screen is really expensive, by comparison).

If an object is shared across threads (bad idea, in general), then the locking overhead protecting the data access and manipulation will generally be vastly greater than the retain/release overhead because of the infrequency of retaining/releasing.


As far as Python's GIL overhead is concerned, I would bet that it has more to do with how often the reference count is incremented and decremented as a part of normal interpreter operations.

As a new Objective-C developer, what memory-related issues should I watch out for using ARC?

  • Circular References. When objects are codependent, they will leak. You will need to mark some references as weak, and Instruments can help you locate these references. These leaks won't even show up as leaks because they hold strong references to each other.

  • Create Autorelease Pools @autorelease to keep autorelease pool sizes down where you create many autoreleased objects (directly or indirectly). Specifically, your program and programs you depend on will autorelease many objects (ARC or otherwise). An autoreleased object is one which will be released "in the future". Every Cocoa program expects an autorelease pool to exist on each thread. This is why you create a new pool when you create a new thread, and why you create one in main. The pools operate as a stack - you may push and pop pools. When a pool is destroyed it sends its deferred release message to every object it holds. This means that really large loops with many temporary allocations may result in many objects which are referenced only by the pool, and the pool may grow very large. For this reason, you drain manage pools directly in some cases to minimize the number of objects that are waiting to be released and deallocated.

  • Use proper bridging/casting. Sometimes you will need to manage lifetimes explicitly. ARC handles the obvious cases, but there are complex cases where you will need to manage lifetimes explicitly.

  • When using malloc'ed and new'ed allocations, as well as opaque types in 'Core' APIs. ARC only manages NSObject types. You still need to explicitly free, delete, and use correct ref counting for these allocations, types, and when interfacing with those APIs.

  • Always follow NS-API naming conventions, and avoid explicit memory management attributes where possible.

  • You'll obviously need MRC when you compile sources without ARC or GC. This is quite common when using/working with other libraries/code bodies. Of course, the compiler handles interactions correctly so your ARC program should not leak.

  • ARC handles most of what you will need if you use proper naming and written style, but there will be a few other corner cases. Fortunately, you can still run Leaks and Zombies to locate these issues if you don't realize them during development.



Related Topics



Leave a reply



Submit