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. IfNSNotificationCenter
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.