5. Memory Management
Memory management is an important concept in any object-oriented language, such as Objective-C. Understanding the specifics of any language’s memory-management model is vital to being able to write memory-efficient, bug-free code.
Once you understand the rules, memory management in Objective-C is not complicated and was made easier when Automatic Reference Counting (ARC) came along. ARC diverts nearly all the memory-management decisions to the compiler, leaving you to concentrate on the business logic.
Item 29: Understand Reference Counting
Objective-C uses reference counting for memory management, meaning that every object has a counter that is incremented and decremented. You increment the counter when you want to register your interest in keeping an object alive, and you decrement the counter when you have finished with it. When an object’s counter reaches zero, the object no longer has anything interested in it and is free to be destroyed. That is a brief overview; understanding the topic fully is crucial to writing good Objective-C code, even if you are going to be using ARC (see Item 30).
The garbage collector for use with Objective-C code written for Mac OS X is officially deprecated as of Mac OS X 10.8 and has never been available for iOS. Understanding reference counting is very important going forward because you can no longer rely on the garbage collector on Mac OS X and you never have, nor will you be able to, on iOS.
If you already use ARC, you should switch off the part of your brain that tells you that most of the code presented won’t compile. That’s true, in an ARC world. But this item is explaining reference counting from an Objective-C perspective, which is still going on with ARC, and to do so requires showing code that uses methods that are explicitly illegal under ARC.
How Reference Counting Works
Under reference-counting architectures, an object is assigned a counter to indicate how many things have an interest in keeping that object alive. This is referred to as the retain count in Objective-C but can also be referred to as the reference count. The following three methods declared on the NSObject protocol can manipulate that counter to either increment it or decrement it:
retain Increment the retain count.
release Decrement the retain count.
autorelease Decrement the retain count later, when the autorelease pool is drained. (The autorelease pool is discussed further on page 150 and in Item 34.)
A method to inspect the retain count, called retainCount, is generally not very useful, even when debugging, so I (and Apple) encourage you not to use it. See Item 36 for more information.
An object is always created with a retain count of at least 1. Interest in keeping the object alive is indicated by invoking the retain method. When that interest has gone because the object is no longer required by a certain portion of code, release or autorelease is called. When the retain count finally reaches 0, the object is deallocated, meaning that its memory is marked for reuse. Once this has happened, any references to that object are no longer valid.
Figure 5.1 illustrates an object going through creation, a retain, and then two releases.
Figure 5.1 An object’s retain count incrementing and decrementing as it goes through its life cycle
Many objects will be created throughout an application’s life cycle. These objects all become related to one another. For example, an object representing a person has a reference to string objects for the person’s names and might also have references to other person objects, such as in a set representing the friends of the person, thereby forming what is known as an object graph. Objects are said to own other objects if they hold a strong reference to them. This means that they have registered their interest in keeping them alive by retaining them. When they are finished with them, they release them.
In the Figure 5.2 object graph, ObjectA is being referenced by both ObjectB and ObjectC. When both ObjectB and ObjectC have finished with ObjectA, its retain count drops to 0 and it can be destroyed. Both ObjectB and ObjectC are being kept alive by other objects, which in turn are being kept alive by other objects. Eventually, if you went up the tree of what is referencing what, you would come to a root object. In the case of Mac OS X applications, this could be the NSApplication object; in the case of iOS applications, the UIApplication object. Both are singletons created when an application launches.
Figure 5.2 An object graph showing an object eventually being deallocated after all references to it are released
The following code will help you to understand this in practice:
NSMutableArray *array = [[NSMutableArray
alloc] init];
NSNumber *number = [[NSNumber
alloc] initWithInt:1337];
[array addObject:number];
[number release];
// do something with 'array'
[array release];
As explained previously, this code won’t compile under ARC, owing to the explicit calls to release. In Objective-C, a call to alloc will result in the return of an object that is said to be owned by the caller. That is to say, the caller’s interest has already been registered because it used alloc. However, it is important to note that this does not necessarily mean that the retain count is exactly 1. It might be more, since the implementation of either alloc or initWithInt: may mean that other retains have been made on the object. What is guaranteed, though, is that the retain count is at least 1. You should always think about retain counts in this way. You should never guarantee what a retain count is, only what effect your actions have had on the retain count: whether that is incremented or decremented.
The number object is then added to the array. In doing this, the array also registers its interest by calling retain on the number object within the addObject: method. At this point, the retain count is at least 2. Then, the number object is no longer required by this code, so it is released. Its retain count is now back down to at least 1. At this point, the number variable can no longer be safely used. The call to release means that the object pointed to is no longer guaranteed to be alive. Of course, the code in this scenario makes it obvious that it will be alive after the call to release, since the array is also still referencing it. However, that should never be assumed, which means that you should not do something like this:
NSNumber *number = [[NSNumber
alloc] initWithInt:1337];
[array addObject:number];
[number release];
NSLog(@"number = %@", number);
Even though the code will work in this scenario, it is not good practice. If for any reason the number object were deallocated while calling release as its retain count dropped to zero, the call to NSLog would potentially crash. The reason I qualify that with “potentially” is that when an object is deallocated, its memory is simply returned to the available pool. If the memory has not been overwritten by the time the NSLog runs, the object will still exist, and there won’t be a crash. For this reason, bugs where objects have been released too early are often difficult to debug.
To mitigate accidentally using an object that is no longer valid, you will often see a release followed by nilling out the pointer. This ensures that nothing can access a pointer to a potentially invalid object, often referred to as a dangling pointer. For example, it can be done like this:
NSNumber *number = [[NSNumber
alloc] initWithInt:1337];
[array addObject:number];
[number release];
number = nil;
Memory Management in Property Accessors
As described earlier, objects form an object graph by being linked together. The array in the example holds onto the objects it contains by retaining them. In the same way, other objects will hold onto other objects, often through the use of properties (see Item 6), which use accessors to get and set instance variables. If the property is a strong relationship, the value of the property is retained. A setter accessor for such a property called foo, backed by an instance variable called _foo, would look like this:
- (void)setFoo:(id)foo {
[foo retain];
[_foo release];
_foo = foo;
}
The new value is retained, and the old value is released. Then the instance variable is updated to point to the new value. The order is important. If the old value was released before the new value was retained and the two values are exactly the same, the release would mean that the object could potentially be deallocated prematurely. The subsequent retain could not resurrect the deallocated object, and the instance variable would be a dangling pointer.
Autorelease Pools
A feature that is important to Objective-C’s reference-counting architecture is what is known as autorelease pools. Instead of calling release to immediately decrement an object’s retain count (and potentially deallocate it), you can also call autorelease, which performs the release sometime later, usually the next time around the event loop, but it can also happen sooner (see Item 34).
This feature is very useful, especially when returning an object from a method. In this case, you don’t always want to return it as owned by the caller. For example, consider the following method:
- (NSString*)stringValue {
NSString *str = [[NSString
alloc]
initWithFormat:@"I am this: %@", self];
return str;
}
In this case, str is returned with a +1 retain count, since the call to alloc returns with a +1 count, and there is no balancing release. The meaning of +1 here is that you as the caller are responsible for one retain. You must somehow balance that one retain. This does not mean that the retain count is exactly 1, however. It might be more, but that is implementation detail within the method initWithFormat:. All you need to worry about is balancing that one retain.
However, you cannot release str inside the method, because it would then immediately be deallocated before being returned. So an autorelease is used to indicate that the object should be released sometime later but guaranteed to be long enough in the future that the returned value can be retained by the caller if it needs to hold onto it. In other words, the object is guaranteed to be alive across the method call boundary. In fact, the release will happen when the outermost autorelease pool is drained (see Item 34), which, unless you have your own autorelease pools, will be next time around the current thread’s event loop. Applying this to the stringValue method gives the following:
- (NSString*)stringValue {
NSString *str = [[NSString
alloc]
initWithFormat:@"I am this: %@", self];
return [str autorelease];
}
Now the returned NSString object will definitely be alive when it is returned to the caller. So the object can be used like this:
NSString *str = [self
stringValue];
NSLog(@"The string is: %@", str);
No extra memory management is required here, since the str object is returned autoreleased and therefore is balanced. Since the release from being in the autorelease pool won’t happen until next time around the event loop, the object does not need to be explicitly retained to be used in the log statement. However, if the object needs to be held onto, such as being set to an instance variable, the object needs to be retained and subsequently released:
_instanceVariable = [[self stringValue] retain];
// ...
[_instanceVariable release];
So autorelease is a way of extending the lifetime of an object just enough so that it can survive across method call boundaries.
Retain Cycles
A common scenario to be aware of with reference counting is what is known as a retain cycle, which occurs when multiple objects reference one another cyclically. This leads to memory leaks because no object in the cycle will ever see its retain count drop to 0. Each object has at least one other object in the cycle maintaining a reference to it. In Figure 5.3, three objects all have a single reference to one of the other two objects. In this cycle, all retain counts are 1.
Figure 5.3 A retain cycle in an object graph
In a garbage-collected environment, this situation would usually be picked up as a so-called island of isolation. In such a scenario, the collector would deallocate all three objects. This luxury is not available under the reference-counting architecture of Objective-C. The problem is usually solved by using weak references (see Item 33) or an external influence causing one of the objects to relinquish its retain of another object. In either case, the retain cycle is broken, and memory is no longer leaked.
Things to Remember
Reference-counting memory management is based on a counter that is incremented and decremented. An object is created with a count of at least 1. An object with a positive retain count is alive. When the retain count drops to 0, the object is destroyed.
As it goes through its life cycle, an object is retained and released by other objects holding references to it. Retaining and releasing increments and decrements the retain count, respectively.
Item 30: Use ARC to Make Reference Counting Easier
Reference counting is a fairly easy concept to understand (see Item 29). The semantics of where retains and releases need to appear are easily expressed. So with the Clang compiler project came a static analyzer that is able to indicate the location of problems with the reference counting. For example, consider the following snippet written with manual reference counting:
if ([self
shouldLogMessage]) {
NSString *message = [[NSString
alloc] initWithFormat:
@"I am object, %p", self];
NSLog(@"message = %@", message);
}
This code has a memory leak because the message object is not released at the end of the if statement. Since it cannot be referenced outside the if statement, the object is leaked. The rules governing why this is a leak are straightforward. The call to NSString’s alloc method returns an object with a +1 retain count. But there is no balancing release. These rules are easy to express, and a computer could easily apply these rules and tell us that the object has been leaked. That’s exactly what the static analyzer does.
The static analyzer was taken one step further. Since it is able to tell you where there are memory-management problems, it should easily be able to go ahead and fix them by adding in the required retain or release, right? That is the idea from which Automatic Reference Counting (ARC) was born. ARC does exactly what it says in the name: makes reference counting automatic. So in the preceding code snippet, the message object would automatically have a release added in just before the end of the if statement scope, automatically turning the code into the following:
if ([self
shouldLogMessage]) {
NSString *message = [[NSString
alloc] initWithFormat:
@"I am object, %p", self];
NSLog(@"message = %@", message);
[message release]; ///< Added by ARC
}
The important thing to remember with ARC is that reference counting is still being performed. But ARC adds in the retains and releases for you. ARC does more than apply memory-management semantics to methods that return objects, as you will see. But it is these core semantics, which have become standard throughout Objective-C, on which ARC is built.
Because ARC adds retains, releases, and autoreleases for you, calling memory-management methods directly under ARC is illegal. Specifically, you cannot call the following methods:
retain
release
autorelease
dealloc
Calling any of these methods directly will result in a compiler error because doing so would interfere with ARC’s being able to work out what memory-management calls are required. You have to put your trust in ARC to do the right thing, which can be daunting for developers used to manual reference counting.
In fact, ARC does not call these methods through the normal Objective-C message dispatch but instead calls lower-level C variants. This is optimal, since retains and releases are performed frequently, and so saving CPU cycles here is a big win. For example, the equivalent for retain is objc_retain. This is also why it is illegal to override retain, release, or autorelease, as these methods are never called directly. For the rest of this item, I will usually talk about the equivalent Objective-C method rather than the lower-level C variants. This should help if your background is with manual reference counting.
Method-Naming Rules Applied by ARC
The memory-management semantics dictated through method names have long been convention in Objective-C, but ARC has cemented them as hard rules. The rules are simple and relate to the method name. A method returning an object returns it owned by the caller if its method name begins with one of the following:
alloc
new
copy
mutableCopy
“Owned by the caller” means that the code calling any of the four methods listed is responsible for releasing the returned object. That is to say, the object will have a positive retain count, where exactly 1 needs to be balanced by the calling code. The retain count may be greater than 1 if the object has been retained additionally and autoreleased, which is one reason why the retainCount method is not useful (see Item 36).
Any other method name indicates that any returned object will be returned not owned by the calling code. In these cases, the object will be returned autoreleased, so that the value is alive across the method call boundary. If it wants to ensure that the object stays alive longer, the calling code must retain it.
ARC automatically handles all memory management required to maintain these rules, including the code for returning objects autoreleased, as illustrated in the following code:
+ (EOCPerson*)newPerson {
EOCPerson *person = [[EOCPerson
alloc] init];
return person;
/**
* The method name begins with 'new', and since 'person'
* already has an unbalanced +1 retain count from the
* 'alloc', no retains, releases, or autoreleases are
* required when returning.
*/
}
+ (EOCPerson*)somePerson {
EOCPerson *person = [[EOCPerson
alloc] init];
return person;
/**
* The method name does not begin with one of the "owning"
* prefixes, therefore ARC will add an autorelease when
* returning 'person'.
* The equivalent manual reference counting statement is:
* return [person autorelease];
*/
}
- (void)doSomething {
EOCPerson *personOne = [EOCPerson
newPerson];
// ...
EOCPerson *personTwo = [EOCPerson
somePerson];
// ...
/**
* At this point, 'personOne' and 'personTwo' go out of
* scope, therefore ARC needs to clean them up as required.
* - 'personOne' was returned as owned by this block of
* code, so it needs to be released.
* - 'personTwo' was returned not owned by this block of
* code, so it does not need to be released.
* The equivalent manual reference counting cleanup code
* is:
* [personOne release];
*/
}
ARC standardizes the memory-management rules through naming conventions, something that newcomers to the language often see as unusual. Very few other languages put as much emphasis on naming as Objective-C does. Becoming comfortable with this concept is crucial to being a good Objective-C developer. ARC helps with the process because it does a lot of the work for you.
In addition to adding in retains and releases, ARC has other benefits. It is also able to perform optimizations that would be difficult or impossible to do by hand. For example, at compile time, ARC can collapse retains, releases, and autoreleases to cancel them out, if possible. If it sees that the same object is being retained multiple times and released multiple times, ARC can sometimes remove pairs of retains and releases.
ARC also includes a runtime component. The optimizations that occur here are even more interesting and should help prove why all future code should be written under ARC. Recall that some objects are returned from methods autoreleased. Sometimes, the calling code needs to retain the object straightaway, as in this scenario:
// From a class where _myPerson is a strong instance variable
_myPerson = [EOCPerson personWithName:@"Bob Smith"];
The call to personWithName: returns a new EOCPerson autoreleased. But the compiler also needs to add a retain when setting the instance variable, since it holds a strong reference. Therefore, the preceding code is equivalent to the following in a world of manual reference counting:
EOCPerson *tmp = [EOCPerson
personWithName:@"Bob Smith"];
_myPerson = [tmp retain];
You would be correct to note here that the autorelease from the personWithName: method and the retain are extraneous. It would be beneficial for performance to remove both. But code compiled under ARC needs to be compatible with non-ARC code, for backward compatibility. ARC could have removed the concept of autorelease and dictated that all objects returned from methods be returned with a +1 retain count. However, that would break backward compatibility.
But ARC does in fact contain runtime behavior to detect the situation of extraneous autorelease plus immediate retain. It does this through a special function that is run when an object is returned autoreleased. Instead of a plain call to the object’s autorelease method, it calls objc_autoreleaseReturnValue. This function inspects the code that is going to be run immediately after returning from the current method. If it is detected that this is going to be a retain of the returned object, a flag is set within a global data structure (processor dependent) instead of performing the autorelease. Similarly, the calling code that retains an autoreleased object returned from a method uses a function called objc_retainAutoreleasedReturnValue instead of calling retain directly. This function checks the flag and, if set, doesn’t perform retain. This extra work to set and check flags is faster than performing autorelease and retain.
The following code illustrates this optimization by showing how ARC uses these special functions:
// Within EOCPerson class
+ (EOCPerson*)personWithName:(NSString*)name {
EOCPerson *person = [[EOCPerson
alloc] init];
person.name = name;
objc_autoreleaseReturnValue(person);
}
// Code using EOCPerson class
EOCPerson *tmp = [EOCPerson
personWithName:@"Matt Galloway"];
_myPerson = objc_retainAutoreleasedReturnValue(tmp);
These special functions have processor-specific implementations to make use of the most optimal solution. The following pseudocode implementations explain what happens:
id objc_autoreleaseReturnValue(id object) {
if ( /* caller will retain object */ ) {
set_flag(object);
return object; ///< No autorelease
} else {
return [object autorelease];
}
}
id objc_retainAutoreleasedReturnValue(id object) {
if (get_flag(object)) {
clear_flag(object);
return object; ///< No retain
} else {
return [object retain];
}
}
The way in which objc_autoreleaseReturnValue detects whether the calling code is going to immediately retain the object is processor specific. Only the author of the compiler can implement this, since it uses inspection of the raw machine-code instructions. The author of the compiler is the only person who can ensure that the code in the calling method is arranged in such a way that detection like this is possible.
This is just one such optimization that is made possible by putting memory management in the hands of the compiler and the runtime. It should help to illustrate why using ARC is such a good idea. As the compiler and runtime mature, I’m sure that other optimizations will be making an appearance.
Memory-Management Semantics of Variables
ARC also handles memory management of local variables and instance variables. By default, every variable is said to hold a strong reference to the object. This is important to understand, particularly with instance variables, since for certain code, the semantics can be different from manual reference counting. For example, consider the following code:
@interface EOCClass : NSObject {
id _object;
}
@implementation EOCClass
- (void)setup {
_object = [EOCOtherClass
new];
}
@end
The _object instance variable does not automatically retain its value under manual reference counting but does under ARC. Therefore, when the setup method is compiled under ARC, the method transforms into this:
- (void)setup {
id tmp = [EOCOtherClass
new];
_object = [tmp retain];
[tmp release];
}
Of course, in this situation, retain and release can be cancelled out. So ARC does this, leaving the same code as before. But this comes in handy when writing a setter. Before ARC, you may have written a setter like this:
- (void)setObject:(id)object {
[_object release];
_object = [object retain];
}
But this reveals a problem. What if the new value being set is the same as the one already held by the instance variable? If this object was the only thing holding a reference to it, the release in the setter would cause the retain count to drop to 0, and the object would be deallocated. The subsequent retain would cause the application to crash. ARC makes this sort of mistake impossible. The equivalent setter under ARC is this:
- (void)setObject:(id)object {
_object = object;
}
ARC performs a safe setting of the instance variable by retaining the new value, then releasing the old one before finally setting the instance variable. You may have understood this under manual reference counting and written your setters correctly, but with ARC, you don’t have to worry about such edge cases.
The semantics of local and instance variables can be altered through the application of the following qualifiers:
__strong The default; the value is retained.
__unsafe_unretained The value is not retained and is potentially unsafe, as the object may have been deallocated already by the time the variable is used again.
__weak The value is not retained but is safe because it is automatically set to nil if the current object is ever deallocated.
__autoreleasing This special qualifier is used when an object is passed by reference to a method. The value is autoreleased on return.
For example, to make an instance variable behave the same as it does without ARC, you would apply the __weak or __unsafe_unretained attribute:
@interface EOCClass : NSObject {
id __weak _weakObject;
id __unsafe_unretained _unsafeUnretainedObject;
}
In either case, when setting the instance variable, the object will not be retained. Automatically nilling weak references with the __weak qualifier is available only in the latest versions of the runtime (Mac OS X 10.7 and iOS 5.0) because they rely on features that have been added.
When applied to local variables, the qualifiers are often used to break retain cycles that can be introduced with blocks (see Item 40). A block automatically retains all objects it captures, which can sometimes lead to a retain cycle if an object retaining a block is retained by the block. A __weak local variable can be used to break the retain cycle:
NSURL *url = [NSURL
URLWithString:@"http://www.example.com/"];
EOCNetworkFetcher *fetcher =
[[EOCNetworkFetcher
alloc] initWithURL:url];
EOCNetworkFetcher * __weak weakFetcher = fetcher;
[fetcher startWithCompletion:^(BOOL success){
NSLog(@"Finished fetching from %@", weakFetcher.url);
}];
ARC Handling of Instance Variables
As explained, ARC also handles the memory management of instance variables. Doing so requires ARC to automatically generate the required cleanup code during deallocation. Any variables holding a strong reference need releasing, which ARC does by hooking into the dealloc method. With manual reference counting, you would have found yourself writing dealloc methods that look like this:
- (void)dealloc {
[_foo release];
[_bar release];
[super
dealloc];
}
With ARC, this sort of dealloc method is not required; the generated cleanup routine will perform these two releases for you by stealing a feature from Objective-C++. An Objective-C++ object has to call the destructors for all C++ objects held by the object during deallocation. When the compiler saw that an object contained C++ objects, it would generate a method called .cxx_destruct. ARC piggybacks on this method and emits the required cleanup code within it.
However, you still need to clean up any non-Objective-C objects if you have any, such as CoreFoundation objects or heap-allocated memory, with malloc(). But you do not need to call the superclass implementation of dealloc as you did before. Recall that calling dealloc under ARC explicitly is illegal. So ARC, along with generating and running the .cxx_destruct method for you, also automatically calls the superclass’s dealloc method. Under ARC, a dealloc method may end up looking like this:
- (void)dealloc {
CFRelease(_coreFoundationObject);
free(_heapAllocatedMemoryBlob);
}
The fact that ARC generates deallocation code means that usually, a dealloc method is not required. This often considerably reduces the size of a project’s source code and helps to reduce boilerplate code.
Overriding the Memory-Management Methods
Before ARC, it was possible to override the memory-management methods. For example, a singleton implementation often overrode release to be a no-op, as a singleton cannot be released. This is now illegal under ARC because doing so could interfere with ARC’s understanding of an object’s lifetime. Also, because the methods are illegal to call and override, ARC makes the optimization of not going through an Objective-C message dispatch (see Item 11) when it needs to perform a retain, release, or autorelease. Instead, the optimization is implemented with C functions deep in the runtime. This means that ARC is able to do optimizations such as the one described earlier when returning an autoreleased object that is immediately retained.
Things to Remember
Automatic Reference Counting (ARC) frees the developer from having to worry about most memory management. Using ARC reduces boilerplate code from classes.
ARC handles the object life cycle almost entirely by adding in retains and releases as it sees appropriate. Variable qualifiers can be used to indicate memory-management semantics; previously, retains and releases were manually arranged.
Method names have always been used to indicate memory-management semantics of returned objects. ARC has solidified these and made it impossible not to follow them.
ARC handles only Objective-C objects. In particular, this means that CoreFoundation objects are not handled, and the appropriate CFRetain/CFRelease calls must be applied.
Item 31: Release References and Clean Up Observation State Only in dealloc
An object going through its life cycle eventually ends up being deallocated, which is where the dealloc method enters. It is called exactly once during the life cycle of an object: when its retain count drops to zero. When exactly it gets called is not guaranteed, though. Also, you may think that you know when it’s going to be called from manually seeing where retains and releases are. But in practice, any library could be manipulating the object without your knowing, causing deallocation to happen at another time. You should never call dealloc yourself. The runtime will call it at exactly the right time for you. Also, after dealloc has been called on an object, that object is no longer valid, and subsequent method calls are invalid.
So what should you do in dealloc then? The main thing to do is to release any references that the object owns. This means releasing any Objective-C objects, something that ARC automatically adds for you into the dealloc method, through the .cxx_destruct automatic method (see Item 30). Any other non-Objective-C objects that the object owns also need releasing. For instance, CoreFoundation objects need to be explicitly released, since they are pure C APIs.
Another usual thing to do in a dealloc method is to clean up any observation behavior that has been set up. If NSNotificationCenter has been used to register the object for certain notifications, this is often a good place to unregister for notifications so that they are not attempted to be sent to a deallocated object, which would certainly cause an application to crash.
A dealloc method looks like this:
- (void)dealloc {
CFRelease(coreFoundationObject);
[[NSNotificationCenter
defaultCenter] removeObserver:self];
}
Note that when using manual reference counting rather than ARC, [super dealloc] should be the last thing done. ARC automatically enforces this, which is another reason why it is a lot easier and safer to use than manual reference counting. With manual reference counting, this method would also have to manually release every Objective-C object owned by the object.
That said, you should certainly not free resources that are potentially expensive or scarce within the system. Such resources are file descriptors, sockets, or large blocks of memory. The dealloc method should not be relied on to be called at any specific time, since something you hadn’t realized might be holding onto the object. In such a situation, you are keeping hold of scarce system resources longer than you need to, which is undesirable. It is usual in these scenarios to implement another method that should be called when the application has finished with the object. The resources’ life cycles are then made deterministic.
An example of an object that might need a cleanup method is one that manages a socket connection to a server. Perhaps it is a connection to a database. Such a class’s interface may look like this:
#import <Foundation/Foundation.h>
@interface EOCServerConnection : NSObject
- (void)open:(NSString*)address;
- (void)close;
@end
The contract for this class would be that the open: method is called to open the connection; then, when finished with the connection, the application calls close. The close must happen before the connection object is deallocated; otherwise, it is deemed to be a programmer error, just as you have to balance retains and releases with reference counting.
Another reason for cleaning up resources in another cleanup method is that the dealloc method is in fact not guaranteed to be run for every created object. There is the edge case of objects that are still around when an application terminates. These objects do not receive the dealloc message. Instead, they are destroyed by the fact that a terminated application’s resources are returned to the operating system. It is an optimization not to call the dealloc method. But this means that you cannot guarantee that it is always called for every object. Mac OS X and iOS applications both have within their application delegates a method that is called on application termination. This method can be used to run any cleanup methods on objects that need to be guaranteed to be cleaned up.
In the case of Mac OS X, the method called at application termination is on NSApplicationDelegate:
- (void)applicationWillTerminate:(NSNotification *)notification
In the case of iOS, the method is on UIApplicationDelegate:
- (void)applicationWillTerminate:(UIApplication *)application
With regard to the cleanup method for objects that manage resources, this should also be called in dealloc to mitigate the case in which the cleanup method was not called. If this does happen, it is often a good idea to output a log line to indicate that a programmer error has occurred. It is a programmer error because close should have been called before the object was deallocated; otherwise, the close method is irrelevant. This log line will alert the programmer to rectify the problem. It’s still good practice to close the resources in dealloc in order to avoid leaks. An example of such a close and dealloc method is as follows:
- (void)close {
/* clean up resources */
_closed = YES;
}
- (void)dealloc {
if (!_closed) {
NSLog(@"ERROR: close was not called before dealloc!");
[self
close];
}
}
Instead of simply logging an error if the close method is not called, you might decide to throw an exception to indicate that a serious programmer error has occurred.
Another thing to be aware of and avoid in dealloc methods is calling other methods. In the preceding example, of course, a method is called in dealloc. But this is a special case: to detect programmer error. It is not ideal to have to call any other methods at all, because the object being deallocated is in a winding-down state. If the other method happens to perform work asynchronously or calls methods that themselves do, the object being deallocated could be completely dead by the time those methods finish doing their work. This can cause all sorts of problems and often results in an application crash because it calls back to tell the object that it has finished. If the object is dead, this call will fail.
Also, the dealloc method is called on the thread in which the final release that caused the retain count to zero occurred. Some methods are required to be run in a certain thread, such as the main thread. If these methods are called from dealloc, there is no safe way to ensure that it is run on the correct thread. Any usual code to force it to be run on the correct thread is not at all safe, because the object is in a deallocating state, and the runtime has already started altering its internal data structures to indicate this.
The avoidance of method calls in dealloc should also go for property accessors, which can be overridden and therefore themselves try to perform work that is unsafe to do during deallocation. Alternatively, the property may be being observed through Key-Value Observation (KVO), and the observer may try to do some work, such as attempting to retain the object, using the object that is being deallocated. Doing so would cause the runtime to get in a completely inconsistent state, and strange crashes would likely result.
Things to Remember
The dealloc method should be used only to release references to other objects and to unregister anything that needs to be, such as Key-Value Observing (KVO) or NSNotificationCenter notifications.
If an object holds onto system resources, such as file descriptors, there should be a method for releasing these resources. It should be the contract with the consumer of such a class to call this close method when finished using the resources.
Method calls should be avoided in dealloc methods in case those methods try to perform asynchronous work or end up assuming that the object is in a normal state, which it won’t be.
Item 32: Beware of Memory Management with Exception-Safe Code
Exceptions are a language feature offered by many modern languages. Exceptions do not exist in pure C but do in both C++ and Objective-C. In fact, in the modern runtime, C++ and Objective-C exceptions are compatible, meaning that an exception thrown from one language can be caught using a handler from the other language.
Even though the error model of Objective-C (see Item 21) states that you should use exceptions only for fatal errors, you may still need code that catches and handles them. Examples are Objective-C++ code or code that interfaces with a third-party library such that you have no control over the exceptions being thrown. Also, some system libraries still make use of exceptions, harking back to the days when exceptions were in common use. For example, Key-Value Observing (KVO) will throw an exception if you attempt to unregister an observer that was not already registered.
When it comes to memory management, exceptions introduce an interesting problem. Inside a try block, if an object is retained and then an exception is thrown before the object has been released, the object will leak unless this case is handled in the catch block. C++ destructors are run by the Objective-C exception-handling routines. This is important for C++ because any object whose lifetime has been cut short by a thrown exception needs to be destructed; otherwise, the memory it uses will be leaked, not to mention all the other system resources, such as file handles, that may not be cleaned up properly.
The destruction of objects automatically by the exception-handling routines is something that is trickier to do in an environment of manual reference counting. Consider the following Objective-C code, which uses manual reference counting:
@try {
EOCSomeClass *object = [[EOCSomeClass
alloc] init];
[object doSomethingThatMayThrow];
[object release];
}
@catch (...) {
NSLog(@"Whoops, there was an error. Oh well...");
}
At first glance, this may seem correct. But what happens if doSomethingThatMayThrow throws an exception? The release on the following line would not be run, because the exception would halt the execution and jump to the catch block. So in this scenario, the object would be leaked if an exception were thrown. That’s not ideal. The way this is solved is to use the @finally block, which is guaranteed to be run once and only once, whether or not an exception is thrown. For example, the code could be transformed into this:
EOCSomeClass *object;
@try {
object = [[EOCSomeClass
alloc] init];
[object doSomethingThatMayThrow];
}
@catch (...) {
NSLog(@"Whoops, there was an error. Oh well...");
}
@finally {
[object release];
}
Note how the object has had to be pulled out of the @try block because it needs to be referenced in the @finally block. This can get very tedious if you have to do this for all objects that need releasing. Also, if the logic is more complex than this, with multiple statements within the @try block, it can be easy to overlook the scenario in which an object might potentially leak. If the object that is leaked is a scarce resource (or manages one), such as a file descriptor or database connection, the leak is potentially disastrous because eventually, the application could end up unnecessarily holding onto all of a system’s resources.
With ARC, the situation is even more serious. The equivalent ARC code for the original code is this:
@try {
EOCSomeClass *object = [[EOCSomeClass
alloc] init];
[object doSomethingThatMayThrow];
}
@catch (...) {
NSLog(@"Whoops, there was an error. Oh well...");
}
Now it is even more of a problem; you can’t use the trick of putting the release in the @finally block, as it is illegal to call release. But surely ARC handles this situation, you are probably thinking. Well, by default, it does not; to do so requires a large amount of boilerplate code to be added to track the objects that potentially need cleaning up if an exception is thrown. This code can severely decrease performance at runtime even when no exceptions are thrown. This code also increases the size of an application significantly with all the extra code that has to be added. These side effects are not ideal.
Although it is not turned on by default, ARC does support emitting this extra code to handle exceptions safely. The code can be turned on by using the compiler flag -fobjc-arc-exceptions. The rationale behind not turning it on by default is that in Objective-C programming, exceptions should be used only when the application will terminate as a result of that exception being thrown (see Item 21). Therefore, if the application is going to terminate anyway, the potential memory leak is irrelevant. There is no point adding the required code to become exception safe if the application is going to terminate.
The one scenario in which the -fobjc-arc-exceptions flag is turned on by default occurs when the compiler is in Objective-C++ mode. C++ already needs to have code similar to the code that ARC would implement, so the performance hit is not as great if ARC adds in its own code to ensure exception safety. Also, C++ makes heavy use of exceptions, so it’s likely that a developer using Objective-C++ would want to use exceptions.
If you are using manual reference counting and must catch exceptions, remember to ensure that code is written in such a way to clean up correctly. If you are using ARC and must catch exceptions, you will need to turn on the -fobjc-arc-exceptions flag. But above all, if you find yourself catching a lot of exceptions, consider refactoring to make use of NSError-style error passing instead, as explained in Item 21.
Things to Remember
When exceptions are caught, care should be taken to ensure that any required cleanup is done for objects created within the try block.
By default, ARC does not emit code that handles cleanup when exceptions are thrown. This can be enabled with a compiler flag but produces code that is larger and comes with a runtime cost.
Item 33: Use Weak References to Avoid Retain Cycles
A typical situation in an object graph is that a cycle of objects all reference each other in some way. When this happens in a reference-counting architecture such as the Objective-C memory-management model, a memory leak will usually occur at some point because eventually, no object in the cycle of objects will be referenced by anything else. Therefore, none of the objects in the cycle can be accessed, but they won’t be deallocated either, since they are all keeping each other alive.
In the most simple of all retain cycles, two objects reference each other. Figure 5.4 shows an example.
Figure 5.4 Retain cycle in which two objects have a strong reference to each other
This sort of retain cycle is easy to understand and detect by looking at code:
#import <Foundation/Foundation.h>
@class EOCClassA;
@class EOCClassB;
@interface EOCClassA : NSObject
@property (nonatomic, strong) EOCClassB *other;
@end
@interface EOCClassB : NSObject
@property (nonatomic, strong) EOCClassA *other;
@end
It is easy to see from this code that there is a potential retain cycle; if the other property of an instance of EOCClassA is set to an instance of EOCClassB and vice versa with the same instances, the retain cycle as shown in Figure 5.4 would occur.
The result of a retain cycle can be a memory leak. When the last remaining reference to any member of the retain cycle is removed, the entire retain cycle is leaked. This means that none of the objects can be accessed any more. In Figure 5.5, a more complex retain cycle involving four objects is leaked when the last remaining reference to ObjectB is removed.
Figure 5.5 Retain cycle being leaked when the remaining reference from the rest of the object graph to any member of the cycle is removed
Objective-C applications on Mac OS X have the option to use a garbage collector, which will detect cycles and deallocate any objects when they cannot be referenced any more. However, the garbage-collected environment was officially deprecated in Mac OS X 10.8 and has never existed on iOS. Therefore, when writing code, it is important to remember the retain-cycle problem and ensure that it doesn’t occur in the first place.
The best way to avoid retain cycles is to make use of weak references. Such references are also referred to as representing nonowning relationships. This can be achieved by using the unsafe_unretained property attribute. Applying this attribute to the previous example would look like this:
#import <Foundation/Foundation.h>
@class EOCClassA;
@class EOCClassB;
@interface EOCClassA : NSObject
@property (nonatomic, strong) EOCClassB *other;
@end
@interface EOCClassB : NSObject
@property (nonatomic, unsafe_unretained) EOCClassA *other;
@end
In this case, EOCClassB instances do not own the EOCClassA instance in the other property. The attribute is called unsafe_unretained to indicate that the value in that property is potentially unsafe and is unretained by the instance. If that object has been deallocated, calling a method on it would likely cause the application to crash. Since this object is not retaining it, it could possibly have been deallocated.
The unsafe_unretained property attribute is semantically equivalent to the assign attribute (see Item 6). However, assign is usually used only for integral types (int, float, structs, etc.), favoring unsafe_unretained for object types. This is a form of self-documentation to make it clear that the property value is potentially unsafe.
Along with ARC came an Objective-C runtime feature that allows safe weak references: a new property attribute named weak, which works in exactly the same way as unsafe_unretained. However, it also automatically sets the property value to nil as soon as the value in the property is deallocated. In the example, the other property of EOCClassB could be changed to this:
@property (nonatomic, weak) EOCClassA *other;
The difference between unsafe_unretained and weak properties is illustrated in Figure 5.6.
Figure 5.6 Difference between unsafe_unretained and weak attributes when the object pointed to by the property is deallocated
When the reference to the instance of EOCClassA is removed, the property unsafe_unretained still points to the instance that is now deallocated. With the weak property, the property points to nil.
However, using weak properties is not an excuse to be lazy. In the preceding example, it should be seen as an error that the instance of EOCClassB is still alive when the referenced EOCClassA object is deallocated. If this ever does happen, it is a bug. You should still aim to ensure that such a situation does not arise. But using weak rather than unsafe_unretained references makes code safe. Rather than crashing, an application may instead display incorrect data. The latter is certainly more desirable should the problem be exhibited for end users. However, it’s still a bug that the weak-referenced object was prematurely destroyed in the first place. An example of this would be a user interface element having a data source property that it queries to get the data to display. Such a property would usually need to be a weak reference (see Item 23). If the data source object was deallocated before the element had finished with it, a weak reference means that it will not crash but won’t display any data.
The general rule is that if you don’t own an object, you should not retain it. One exception to that rule is collections, which often don’t directly own their contents but are retaining them on behalf of the object that owns the collection. An example of an object having a reference to something it doesn’t own is the Delegate pattern (see Item 23).
Things to Remember
Retain cycles can be avoided by making certain references weak.
Weak references may or may not be autonilling. Autonilling is a new feature introduced with ARC and is implemented in the runtime. Autonilling weak references are always safe to read, as they will never contain a reference to a deallocated object.
Item 34: Use Autorelease Pool Blocks to Reduce High-Memory Waterline
Objective-C objects going through their lives are subjected to reference counting (see Item 29). One of the features of Objective-C’s reference-counted architecture is a concept known as autorelease pools. Releasing an object means that its retain count either is decremented immediately through a call to release or is added to an auto-release pool through a call to autorelease. An autorelease pool is used as a collection of objects that will need releasing at some point in the future. When a pool is drained, all the objects in the pool at that time are sent the release message.
The syntax for creating an autorelease pool is as follows:
@autoreleasepool {
// ...
}
If no autorelease pool is in place when an object is sent the autorelease message, you will see a message like this in the console:
Object 0xabcd0123 of class __NSCFString autoreleased
with no pool in place - just leaking - break on objc_
autoreleaseNoPool() to debug
However, you generally do not need to worry about autorelease pools. An application running on either Mac OS X or iOS is running in a Cocoa (or Cocoa Touch for iOS) environment. Threads that the system creates for you, such as the main thread or threads created as part of Grand Central Dispatch (GCD), have an implicit autorelease pool that is drained each time the thread goes through its event loop. Therefore, you don’t have to create your own autorelease pool blocks. Often, the only one you will ever see in an application is the one that wraps the main application entry point in the main function. For example, an iOS application’s main function usually looks like this:
int main(int argc, char *argv[]) {
@autoreleasepool {
return
UIApplicationMain(argc,
argv,
nil,
@"EOCAppDelegate");
}
}
Technically, this autorelease pool block is unnecessary. The end of the block coincides with the application terminating, at which point the operating system releases all memory used by the application. Without it, any objects autoreleased by the UIApplicationMain function would not have a pool to go into and would log a warning saying just that. So this pool can be thought of as an outer catch-all pool.
The braces in the following code define the scope of the autorelease pool. A pool is created at the first brace and is automatically drained at the end of the scope. Any object autoreleased within the scope is therefore sent the release message at the end of the scope. Autorelease pools can be nested. When an object is autoreleased, it is added to the innermost pool. For example:
@autoreleasepool {
NSString *string = [NSString
stringWithFormat:@"1 = %i", 1];
@autoreleasepool {
NSNumber *number = [NSNumber
numberWithInt:1];
}
}
In this example, two objects are created using class factory methods, which return objects autoreleased (see Item 30). The NSString object will be added to the outer autorelease pool, and the NSNumber object will be added to the inner autorelease pool. This nesting of autorelease pools can be taken advantage of to allow the control of the high memory mark of an application.
Consider the following portion of code:
for (int i = 0; i < 100000; i++) {
[self
doSomethingWithInt:i];
}
If the doSomethingWithInt: method creates temporary objects, they will likely make their way into the autorelease po00ol. These objects may be temporary strings, for example. Even though they are not used after the end of the method, these objects may still be alive because they are in the autorelease pool, ready to be released and subsequently deallocated. But the pool is not drained until the next time around that thread’s event loop. This means that as the for loop executes, more and more objects will be created and added to the autorelease pool. Eventually, once it has finished, the objects will be released. But throughout the execution of the loop, the memory footprint of the application will have been increasing steadily and then would suddenly come back down as all the temporary objects are finally released.
This situation is not ideal, especially if the length of such a loop is arbitrary, depending on user input. Consider reading in a set of objects from a database, for example. Such code may look like this:
NSArray *databaseRecords = /* ... */;
NSMutableArray *people = [NSMutableArray
new];
for (NSDictionary *record in databaseRecords) {
EOCPerson *person = [[EOCPerson
alloc]
initWithRecord:record];
[people addObject:person];
}
The initializer for EOCPerson may create extra temporary objects, just as in the earlier example. If the number of records is large, so too is the number of temporary objects kept alive for longer than is strictly necessary. This is where adding in an autorelease pool can help. If the inner part of the loop is wrapped in an autorelease pool block, anything autoreleased inside the loop is added to that pool rather than the thread’s main pool. For example:
NSArray *databaseRecords = /* ... */;
NSMutableArray *people = [NSMutableArray
new];
for (NSDictionary *record in databaseRecords) {
@autoreleasepool {
EOCPerson *person =
[[EOCPerson
alloc] initWithRecord:record];
[people addObject:person];
}
}
With this new autorelease pool added, the high-memory waterline of the application is lowered during the loop’s execution. The high-memory waterline refers to the highest memory footprint of an application during a certain period. Adding in an autorelease pool block can reduce this footprint because some objects will be deallocated at the end of the block. These are the temporary objects as described earlier.
Autorelease pools can be thought of as being in a stack. When an autorelease pool is created, it is pushed onto the stack; when it is drained, it is pulled off the stack. When an object is autoreleased, it is put into the topmost pool in the stack.
The need to make this additional pool optimization depends entirely on your application. It is certainly not something that should be done without first monitoring the memory footprint to decide whether a problem needs addressing. Autorelease pool blocks do not incur too much overhead, but they do incur at least some overhead, so if the extra autorelease pool can be avoided, it should be.
If you were an Objective-C programmer before the transition to ARC, you will remember the old syntax that used an object called NSAutoreleasePool. This special object wasn’t the same as a normal object, designed to represent an autorelease pool just like the new block syntax. Instead of draining the pool every time round a for loop, this object was more heavyweight, and it was usual to create one pool and drain it every so often, like this:
NSArray *databaseRecords = /* ... */;
NSMutableArray *people = [NSMutableArray
new];
int i = 0;
NSAutoreleasePool *pool = [[NSAutoreleasePool
alloc] init];
for (NSDictionary *record in databaseRecords) {
EOCPerson *person = [[EOCPerson
alloc]
initWithRecord:record];
[people addObject:person];
// Drain the pool only every 10 cycles
if (++i == 10) {
[pool drain];
}
}
// Also drain at the end in case the loop is not a multiple of 10
[pool drain];
This style of code is no longer necessary. Along with the new syntax, ARC brought much more lightweight autorelease pools. So if you have code that used to drain every n iterations of a loop, you can replace it with an autorelease pool block surrounding the contents of the for loop, meaning that a pool is created and drained for every single iteration.
Another benefit of the @autoreleasepool syntax is that every autorelease pool has an associated scope, which helps avoid accidentally using an object that was deallocated by an autorelease pool draining. For example, consider the following code in the old style:
NSAutoreleasePool *pool = [[NSAutoreleasePool
alloc] init];
id object = [self
createObject];
[pool drain];
[self
useObject:object];
This is slightly exaggerated, but it explains the point. The call to useObject: is passing a potentially deallocated object. However, the same code in the new style would look like this:
@autoreleasepool {
id object = [self
createObject];
}
[self
useObject:object];
This time, the code would not even compile, because the object variable is not valid outside of its enclosing scope, so it cannot be used in the call to useObject:.
Things to Remember
Autorelease pools are arranged in a stack, with an object being added to the topmost pool when it is sent the autorelease message.
Correct application of autorelease pools can help reduce the high-memory waterline of an application.
Modern autorelease pools using the new @autoreleasepool syntax are cheap.
Item 35: Use Zombies to Help Debug Memory-Management Problems
Debugging memory-management issues can be painful. Sending a message to a deallocated object is completely unsafe, as one would expect. But sometimes it works, and sometimes it doesn’t. It all depends on whether the memory where the object used to reside has been overwritten. Whether or not the memory is reused is nondeterministic, so a crash may happen only occasionally. Other times, the memory will be only partially reused, so certain bits of the object are still valid. Yet other times, the memory will by sheer fluke have been overwritten with another valid, live object. In these cases, the runtime will send the message to the new object, to which it may or may not respond. If it does, the app won’t crash, but you’ll wonder why objects you didn’t expect to be receiving messages are. If it doesn’t respond to that selector, the application will usually crash.
Fortunately, Cocoa’s “zombies” feature can come in handy. When this debugging feature is enabled, the runtime turns all deallocated instances into a special zombie object rather than deallocating them. The core memory where the object is located is not made available for reuse; therefore, nothing will ever overwrite it. When it receives any message, a zombie object throws an exception saying exactly what message was sent and what the object used to be when it was still alive. Using zombies is the best way to debug memory-management problems.
The feature is turned on by setting the NSZombieEnabled environment variable to YES. For example, if you’re using bash and running an application on Mac OS X, you would do something like this:
export NSZombieEnabled="YES"
./app
When a message is sent to a zombie, a message will be printed to the console, and the application will terminate. The message will look like this:
*** -[CFString respondsToSelector:]: message sent to
deallocated instance 0x7ff9e9c080e0
It is also possible to turn on the option in Xcode such that the environment variable is automatically set when the application is run from within Xcode. To do this, you edit the application’s scheme, select the Run configuration, then the Diagnostics tab, and finally turn on Enable Zombie Objects. Figure 5.7 shows the dialog that you should see in Xcode, with the option to enable zombies turned on.
Figure 5.7 Enabling zombie objects from Xcode’s scheme editor
So how does the zombies feature work? It is implemented deep within the Objective-C runtime and the Foundation and CoreFoundation frameworks. When an object is being deallocated, an additional step is made by using the environment variable if this feature is enabled. This extra step turns the object into a zombie rather than fully deallocating it.
To see what this extra step does, consider the following code:
#import <Foundation/Foundation.h>
#import <objc/runtime.h>
@interface EOCClass : NSObject
@end
@implementation EOCClass
@end
void PrintClassInfo(id obj) {
Class cls = object_getClass(obj);
Class superCls = class_getSuperclass(cls);
NSLog(@"=== %s : %s ===",
class_getName(cls), class_getName(superCls));
}
int main(int argc, char *argv[]) {
EOCClass *obj = [[EOCClass
alloc] init];
NSLog(@"Before release:");
PrintClassInfo(obj);
[obj release];
NSLog(@"After release:");
PrintClassInfo(obj);
}
This code uses manual reference counting to make it easier to show what happens when an object becomes a zombie. ARC would ensure that the str object was alive for as long as it needed to be, meaning that it would never become a zombie in this simple scenario. That’s not to say that objects can never become zombies under ARC. This type of memory bug can still occur with ARC but usually manifests itself through slightly more complex code.
The code in the example has a function to print out the class and superclass names of a given object. The code uses object_getClass(), a runtime function, rather than sending the class Objective-C message. If the object is a zombie, sending any Objective-C message will cause the zombie error message to be printed out and the application to crash. The output of the code looks like this:
Before release:
=== EOCClass : NSObject ===
After release:
=== _NSZombie_EOCClass : nil ===
The object’s class has changed from EOCClass to _NSZombie_EOCClass. But where did this class come from? It hasn’t been defined in the code. Also, it would be fairly inefficient for the compiler to create an extra class for every class it finds, just in case zombies are enabled. What happens is that this _NSZombie_EOCClass is generated at runtime the first time an object of class EOCClass is turned into a zombie. This uses the powerful runtime functions that can manipulate the class list.
The zombie class is a duplicate of a template class called _NSZombie_. These zombie classes don’t do much but simply act as a marker. You’ll see how they act as a marker shortly. First, consider the following pseudocode showing how the zombie class is created if necessary and then how it is used to turn the deallocating object into a zombie.
// Obtain the class of the object being deallocated
Class cls = object_getClass(self);
// Get the class's name
const
char *clsName = class_getName(cls);
// Prepend _NSZombie_ to the class name
const
char *zombieClsName = "_NSZombie_" + clsName;
// See if the specific zombie class exists
Class zombieCls = objc_lookUpClass(zombieClsName);
// If the specific zombie class doesn't exist,
// then it needs to be created
if (!zombieCls) {
// Obtain the template zombie class called _NSZombie_
Class baseZombieCls = objc_lookUpClass("_NSZombie_");
// Duplicate the base zombie class, where the new class's
// name is the prepended string from above
zombieCls = objc_duplicateClass(baseZombieCls,
zombieClsName, 0);
}
// Perform normal destruction of the object being deallocated
objc_destructInstance(self);
// Set the class of the object being deallocated
// to the zombie class
objc_setClass(self, zombieCls);
// The class of 'self' is now _NSZombie_OriginalClass
This routine is what becomes NSObject’s dealloc method. When it sees that the NSZombieEnabled environment variable is set, the runtime swizzles (see Item 13) the dealloc method for a version that performs the preceding code. At the end of this routine, the class of the object has been changed to _NSZombie_OriginalClass, where OriginalClass is the name of the class that once was.
Crucially, the memory the object lives in is not freed (through a call to free()); therefore, the memory will not be available for use again. Although this is leaking memory, this is a debugging tool only and would never be turned on for production-running applications, so it doesn’t matter.
But why create a new class for each class that is turned into a zombie? This is done so that the original class can be determined when a message is sent to a zombie. If all zombies were of class _NSZombie_, the original class name would be lost. Creating a new class is done by using the runtime’s function objc_duplicateClass(), which copies the entire class but gives it a new name. The superclass, instance variables, and methods of the duplicate class will be identical to the one being copied. Another way to achieve the same goal of maintaining the old class name would be to create the new class as inheriting from _NSZombie_ rather than copying it. However, the functions to do this are less efficient than performing a direct copy.
The zombie class comes into action within the forwarding routines (see Item 12). The _NSZombie_ class (and therefore all its copies) do not implement any methods. The class does not have a superclass and is therefore a root class, just like NSObject, with a single instance variable, called isa, which all Objective-C root classes must have. This lightweight class does not implement any methods, so whenever it is sent any message, it will go through the full forwarding mechanism (see Item 12).
At the heart of the full forwarding mechanism is ___forwarding___, a function you may have seen in backtraces while debugging. One of the first things that this function does is check the name of the class of the object being sent a message. If this name is prefixed with _NSZombie_, a zombie has been detected, and something special happens. The application is killed at this point, after printing out a message (shown at the start of this item) to indicate what message was sent and to what type of class. That’s where the fact that the class name has the original class name within it comes in handy. The _NSZombie_ is removed from the start of the zombie class name to leave just the original name. Pseudocode showing what happens is as follows:
// Obtain the object's class
Class cls = object_getClass(self);
// Get the class's name
const
char *clsName = class_getName(cls);
// Check if the class is prefixed with _NSZombie_
if (string_has_prefix(clsName, "_NSZombie_") {
// If so, this object is a zombie
// Get the original class name by skipping past the
// _NSZombie_, i.e. taking the substring from character 10
const
char *originalClsName = substring_from(clsName, 10);
// Get the selector name of the message
const
char *selectorName = sel_getName(_cmd);
// Log a message to indicate which selector is
// being sent to which zombie
Log("*** -[%s %s]: message sent to deallocated instance %p",
originalClsName, selectorName, self);
// Kill the application
abort();
}
The action of this routine can be seen if the example is extended to attempt to message the zombie EOCClass object:
EOCClass *obj = [[EOCClass
alloc] init];
NSLog(@"Before release:");
PrintClassInfo(obj);
[obj release];
NSLog(@"After release:");
PrintClassInfo(obj);
NSString *desc = [obj description];
If this is run with zombies enabled, the following is seen on the console:
Before release:
=== EOCClass : NSObject ===
After release:
=== _NSZombie_EOCClass : nil ===
*** -[EOCClass description]: message sent to deallocated
instance 0x7fc821c02a00
As you can see, this clearly shows what selector was sent and the original class of the object, as well as the pointer value of the dead object that was messaged. This information can be used if you are in a debugger for further analysis, if required, and can prove invaluable with the correct tools, such as Instruments, which ships with Xcode.
Things to Remember
When an object is deallocated, it can optionally be turned into a zombie instead of being deallocated. This feature is turned on by using the environment flag NSZombieEnabled.
An object is turned into a zombie by manipulating its isa pointer to change the object’s class to a special zombie class. A zombie class responds to all selectors by aborting the application after printing a message to indicate what message was sent to what object.
Item 36: Avoid Using retainCount
Objective-C uses reference counting for memory management (see Item 29). Each object has a counter that determines how many other things are interested in keeping it alive. When an object is created, the retain count is greater than zero. Retaining and releasing manipulate the count by incrementing and decrementing it, respectively. When the count reaches zero, the object is deallocated and destroyed.
A method defined by the NSObject protocol allows you to obtain the current retain count for an object:
- (NSUInteger)retainCount
However, ARC has deprecated this method. In fact, the compiler will throw an error if you try to call it when using ARC, just as it does with such methods as retain, release, and autorelease. Even though this method is officially deprecated, it is often misunderstood, and its use should be avoided. If you are not using ARC, which you really should be, it’s still possible to use this method without getting a compiler error. So it is still important to understand why this method is to be avoided.
It may seem as though this method does a reasonable and useful job. It returns the retain count, after all, which is clearly an important piece of information about every object. The problem is, though, that the absolute retain count is often completely irrelevant and not what you need to know. Even if you use this method only for debug purposes, it’s usually not at all helpful.
The first important reason this method is not useful is that its value is the retain count at a given time. Because this value does not include any decrements that are going to happen during a subsequent autorelease pool drain (see Item 34), the value is not necessarily a true representation of the count. Therefore, the following code is very bad:
while ([object retainCount]) {
[object release];
}
The first reason this code is wrong is that it will keep dropping the retain count until the object is deallocated, without regard for any autoreleases that may be pending. If the object was also in an autorelease pool at the time, when that pool is drained, the object is further released, and a crash will certainly occur.
Second, the code is dangerous because retainCount will never return 0; an optimization within the release behavior of objects means that an object is deallocated when it is released if its retain count is 1. Otherwise, the decrement happens. Therefore, the retain count should never officially reach 0. Unfortunately, this code does sometimes work, largely through sheer luck rather than judgment. Modern runtimes usually simply crash when the while loop iterates after the object was eventually deallocated.
Such code should never be necessary. Memory management should be done before this sort of code would ever need to be implemented. There should never be unbalanced retains leaving objects with a positive retain count when you thought they should be deallocated. In such a scenario, the problem should be diagnosed by working out what is still retaining the object and why it hasn’t released it.
You may also try to use the retain count and wonder why the value sometimes looks very large. For example, consider the following code:
NSString *string = @"Some string";
NSLog(@"string retainCount = %lu", [string retainCount]);
NSNumber *numberI = @1;
NSLog(@"numberI retainCount = %lu", [number retainCount]);
NSNumber *numberF = @3.141f;
NSLog(@"numberF retainCount = %lu", [numberFloat retainCount]);
The output of that code on Mac OS X 10.8.2, 64-bit compiled with Clang 4.1, is this:
string retainCount = 18446744073709551615
numberI retainCount = 9223372036854775807
numberF retainCount = 1
The first number is 264 – 1 and the second number is 263 – 1. The retain counts of these objects are both very large because they are representing singleton objects. NSString is implemented as a singleton object, if possible. It is possible if the string is a compile-time constant, as in the example. In this case, the compiler makes a special object, placing the data for the NSString object within the application binary, and uses that instead of creating an NSString object at runtime. NSNumber does a similar thing, using a concept known as tagged pointers for certain types of values. In this scheme, there is no NSNumber object; rather, the pointer value itself holds all the information about the number. The runtime then detects that a tagged pointer is being used during message dispatch (see Item 11) and manipulates the pointer value accordingly to behave as though there were a full NSNumber object. This optimization is done only for certain cases, though, which is why the floating-point number in the example has a retain count of 1, as it does not use this optimization.
In addition, the retain counts of singletons such as these never change. Retains and releases are no-ops. The fact that the retain count can behave like this and even that the two singletons return different values for the retain count should indicate again that it is not always a good value to consider. If you were relying on NSNumber objects to have incrementing and decrementing retain counts and then tagged pointers were implemented post facto, your code would be wrong.
But what if you want to use the retain count for debugging purposes? Even then, it is usually not helpful. The retain count might not be as accurate as you think, owing to the object’s being in an autorelease pool. Also, other libraries can interfere with the retain count by retaining and/or releasing the object. If you inspect the count, you may incorrectly assume that it has changed because of your code rather than from deep within another library. Take, for example, the following code:
id object = [self createObject];
[opaqueObject doSomethingWithObject:object];
NSLog(@"retainCount = %lu", [object retainCount]);
What is the retain count? It could be anything. The call to doSomethingWithObject: may have added the object to multiple collections, retaining it in the process. Or the call may have retained the object multiple times and autoreleased it multiple times, some of which are still pending an autorelease pool drain. The retain count is therefore unlikely to be useful to you.
When can you use retainCount? The best answer: never, especially given that Apple has officially deprecated it when using ARC.
Things to Remember
The retain count of an object might seem useful but usually is not, because the absolute retain count at any given time does not give a complete picture of an object’s lifetime.
When ARC came along, the retainCount method was deprecated, and using it causes a compiler error to be emitted.