In the first installment of Ask Big Nerd Ranch, Juan Pablo Claude discusses a subject that causes much confusion among new Objective-C programmers. Juan Pablo Claude is the co-author, with Aaron Hillegass, of the forthcoming More Cocoa Programming for Mac OS X.
A question from Zack Brown: "When using an object in Objective-C, why doesn't
[self dealloc]get called when the object is released?"
I am delighted that the first question touches on the fundamentals of Cocoa memory management. Memory management concepts are of critical importance and every Cocoa programmer should take the time to master them.
Memory management in Cocoa comes in two flavors: classic retain counts and garbage-collected. The question deals with the retain count flavor, so I will focus on that one.
The retain count memory management scheme in Cocoa works on the basis that an object will remain in memory as long as other objects claim to be "interested in it." The Objective-C runtime keeps track of this "interested in" relationship with a simple number: the retain count. It does not matter who is interested in the object, only if anyone is interested in it. Objects that want to keep another object in memory ask that the other's retain count be increased by one, and if they are no longer interested in it may request that it be decreased by one. When the retain count for an object reaches zero, it means that no one is interested in it and it can be destroyed. The methods needed to alter the retain count of an object are inherited from NSObject:
- (id)retain; // Increases the retain count of the object by 1
- (oneway void)release; // Decreases the retain count of the object by 1
The oneway qualifier in the -release return type indicates that the caller will not wait for an answer from the method. This is required for a nifty technology called Distributed Objects, which allows an application to use objects from another process, even from a remote machine.
The value of an object’s retain count can be obtained with the method:
The following snippets illustrate the basic use of these methods. Any class derived from NSObject and created with -alloc begins life with a retain count of one.
NSObject *anObject = [[NSObject alloc] init];
NSLog (@"Retain count = %d", [anObject retainCount]); // Outputs 1
[anObject release]; // Done with the object
// Elsewhere in the program:
- (void)doSomethingWith:(NSObject *)anObject
NSLog (@"Retain count = %d", [anObject retainCount]); // Outputs 2
The example above begins by creating an object with a retain count of one. It is then passed to another method that increases its retain count, does some work with it and then releases it. At this point, anObject has a retain count of one again, so when it is released again after the -doSomethingWith: method returns, it will be destroyed.
You now know that whenever the retain count for an object reaches zero, it will be destroyed. This fact puts a burden of responsibility on you, the programmer. First of all, it means that if you release an object that already has a retain count of zero, your program will crash, because you will be sending a message to an object that no longer exists. Second, if you forget to release an object you no longer need, it will stay in memory while the program runs and waste resources. This situation is known as a memory leak.
When an object is released and reaches a retain count of zero, it is given the opportunity to clean-up after itself. This done by implementing the -dealloc method that is called just before the object is destroyed. In -dealloc you will typically release any objects or resources that are owned by the released object, so that memory leaks are prevented. A typical situation is given below.
[currentDate release]; // current date is an instance variable
[myView release]; // myView is an instance variable
Notice how the -dealloc method ends by calling the superclass's -dealloc. That is required and guarantees that clean-up continues all the way up to NSObject. The -dealloc method should never be invoked directly by the programmer. It must be called indirectly as a result of a -release message that decreases the retain count of an object to zero.
The final concept needed to complete this discussion of retain count memory management is autoreleased objects. The need for autoreleased objects arises from the situation illustrated by the method below.
- (MyObject *)returnsAnObject
MyObject *foo = [[MyObject alloc] init];
// Do something with foo
The -returnsAnObject method creates an instance of MyObject, manipulates it and then returns it. As written, the object is returned with a retain count of one. That may be OK, but it means that the object will have to be released explicitly later and this could cause confusion if the -returnsAnObject method is part of a library and you have no access to the source code. Adding a [foo release]; statement before returning will result in a crash because the object will be destroyed just before returning it.
The solution is to use the -autorelease method:
- (id)autorelease; // Returns the object being autoreleased.
The -autorelease method means that the object will be released but later on, typically after the current user event is processed. For programming purposes, you can consider that an autoreleased object has a retain count of zero.
The previous example can then be amended to:
- (MyObject *)returnsAnObject
MyObject *foo = [[[MyObject alloc] init] autorelease];
// Do something with foo
return foo; // foo is returned with a virtual retain count of zero
The memory management concepts just discussed lead to the following retain count rules:
At this point I can finally answer the reader's question as to why -dealloc may not be called after releasing an object. One possibility is that the retain count has not reached zero yet. Perhaps a -release message has been forgotten and the retain count is higher than the reader thinks. To find out if that is the case, one can log the retain count of the object just before the offending release:
NSLog(@"The retain count is: %d", [anObject retainCount]);
Another debug strategy is to use the Clang Static Analyzer included in the Snow Leopard developer tools. In Xcode's Build menu there is a Build and Analyze item that scans your code looking for trouble. The static analyzer is capable of keeping track of retain counts and detecting when an object is over- or under-released. The analyzer essentially walks every line of your code, considering every possible execution branch and looking for errors that are not apparent at compile time. Using the static analyzer can be a humbling experience, but it is a powerful tool that should become part of your coding routine.
Another explanation as to why a -dealloc method may not be called when the reader expects is that dealloc is not guaranteed to be called when an application terminates. If the user quits an application, it may be more efficient to let the operating system clear the application memory and any objects remaining in it, than to let Cocoa do it before terminating the application. For this reason, one should not rely on -dealloc to manage the release of scarce system resources.
Want an answer to your Mac programming question? Submit your question to www.informit.com/askbnr.
Take advantage of special member promotions, everyday discounts, quick access to saved content, and more! Join Today.