Tuesday, September 1, 2009

iphone tutorial: Memory management - autorelease and NIB files

In this tutorial we're going to dive a bit deeper into Cocoa memory management, specifically looking at a feature called autorelease. If you haven't read the first part of the memory management tutorial, it's highly recommended that you do.


Cocoa memory ownership rules


In the first part of this tutorial we introduced the concept of memory ownership. To help deciding who actually owns a memory block, the Cocoa designers have developed a set of rules that all Cocoa classes and applications should follow. This set of rules can be found in the "Memory Management Programming Guide for Cocoa":


---

To make sure it is clear when you own an object and when you do not, and what responsibilities you have as an owner, Cocoa sets the following policy:


You own any object you create.

You “create” an object using a method whose name begins with “alloc” or “new” or contains “copy” (for example, alloc, newObject, or mutableCopy).


If you own an object, you are responsible for relinquishing ownership when you have finished with it. You relinquish ownership of an object by sending it a release message or an autorelease message (autorelease is discussed in more detail in “Delayed Release”). In Cocoa terminology, relinquishing ownership of an object is typically referred to as “releasing” an object.


If you do not own an object, you must not release it.

---


If you think these rules are too complicated to remember, try to at least remember the naming scheme; methods beginning with "alloc" or "new" or contains "copy" creates an object (and returns a reference to that object) that you are responsible for freeing. That is, these methods, transfer the ownership to you, the caller of the moethod.


A new concept is also introduced; autorelease or "delayed release". If you call 'autorelease' on an object reference, that object won't be released immediately, but "later". Before we explain what later means, it might be good to understand why there is such a feature as a "delayed release".


Autorelease


Why would you want to release an object later instead of now or not at all? One example is when you have a method that creates objects for someone else than itself.


(MyClass.m)

+(MyClass *)createObject {

MyClass *reference = [[MyClass alloc] init];

return reference;

}


The method 'createObject' in the class MyClass allocates and initialises a MyClass object and then returns the reference to the newly created object. An application calling this method


(MyApplication.m)

MyClass *myClass = [MyClass createObject];


would not be responsible for releasing the object since the 'createObject' method name doesn't contain any of the "magic words" listed in the rules above. The 'createObject' method on the other hand calls 'alloc' which is a magic word and is therefore responsible for calling release.


It's impossible to call 'release' on the created object after the return statement and 'release' is called before the return statement, the object will be deallocated and the method will return a reference to a deallocated object which will lead to chaos. So when should 'release' be called? Well, "later"...


Here "later" means "after the return statement" and late enough so that the caller of the method gets a chance to store the returned release in a variable (or somewhere else) and call 'retain' on it.


This is where the autorelease mechanism steps in and provides a solution. If the method creating the object calls 'autorelease' on the reference instead of 'release' evertyhing will work; the method has fulfilled its responsibility to release the object it created and the caller of the method gets a chance to call retain on the returned object.


(MyClass.m)

+(MyClass *)createObject {

MyClass *reference = [[MyClass alloc] init];

[reference autorelease]; // call autorelease on the newly create object

return reference;

}


(MyApplication.m)

MyClass *myClass = [MyClass createObject];

[myClass retain]; // call retain on the returned object to protect it from being deallocated


The autorelease method can be called anywhere the release method should have been called but just as with release it's important to call it the right number of times; for each time 'autorelease' is called 'release' will be called "later".


NIB files


We mentioned above that a method that creates an object for someone else is a typical user of the autorelease functionality. Another user of autorelease is a NIB file, or actually the method that load it. A NIB file contains a graph of objects where most objects are connected to each other using outlets. When the loader method rebuilds this object graph in memory, using the NIB file as a blueprint, it creates all objects with a reference count of 1 and then autorelease them.


When the connections between the objects are re-established, the loader method calls setter methods of the objects, which should retain the object reference if they want to prevent the newly loaded object from being deallocated. Ownership is transferred to these objects, meaning that the objects also are responsible for releasing the references in their 'dealloc' method. Setter methods for outlets are normally synthesized using the @property/@synthesize mechanism of Objective C.


@property (nonatomic, retain) IBOutlet someClass *someOutlet;


The 'retain' keyword of the property declaration will ensure that the corresponding setter method created by the @synthesize keyword will retain the reference passed to it.


However, there are also top-level objects in the file that have no natural owner. If you want to keep those objects around, which you normally do, you have to manually retain them (and then later manually releasing them). But how do you get hold of the references to them if they have no "owners"? Well, they do actually get a temporary owner during the loading process; references to all the top-level objects are stored in an array, returned by the 'loadNibNamed:owner:options' method.


As a simple alternative to iterating through the array and retain all the objects in it individually you could retain the array itself instead (and then later release it). This works since it prevents the array from being (auto) released and deallocated - if it was it would release all the references stored in it (decreasing the reference count to zero) and thus force all the objects in the array to be deallocated.


NSArray *nibArray = [[NSBundle mainBundle] loadNibNamed:@"Nib1" owner:self options:nil];

if ( nibArray == nil ) {

NSLog(@"loadNibNamed failed");

return;

}

[nibArray retain];


NIB files with "unconnected" top-level objects aren't that common since it would be useless to define a lot of objects in a NIB that no one uses. Much more common is that the top-level objects are connected to outlets in the "File's owner" object. When that is the case you don't have to worry about implicitly retaining the objects since they will be retained by the setter methods in the "File's owner".


7 comments:

  1. Great writeup, very informative. Being new to iphone dev I did have a followup question though. I've spent a fair amount of time reading through code samples for the iphone SDK and I've noticed autorelease is heavily used in a lot of cases. If method and class variables are set to all autorelease do they just sit in memory until the app exits (where they should be released by the OS anyway) or are they released when the method falls out of scope?

    ReplyDelete
  2. @Alex. The question of when autoreleased objects get released is a very good one. As far as I understand the only place this happens in a normal application is in main.m, where

    [pool release]

    is called after UIApplicationMain() returns.

    This means, just as you suspected, that they won't be released until the app exits. I actually don't know if there are any other places this happens, but have read some sketchy information that suggests there is.

    Therefore, if you are going to create a lot of autoreleased objects, either explicitly by doing it in your own code, or implicitly by calling a lot of library methods which create objects, you can create your own pool and autorelease it more often. For example in each iteration of your "main loop" if you have such a design.

    The good thing is that autorelease pools can be "stacked"/"nested" and autorelease automatically uses the "top"/"innermost" pool.

    Basically, what you need to do is exactly the same thing that is performed in main.m. If you have a performance critical application it might be worth investigating which part of your code that should be "wrapped" in a new local pool. Is it better to wrap a big chunk of code (main loop) or try to pinpoint a specific method or block of code which does most of the autoreleasing?

    ReplyDelete
  3. Yea I see what you're saying. Autorelease seems like a bit of a beast unless its pretty well understood (and not nearly as "auto" as the name suggests =P)

    Coming from the .NET world I had some assumptions it would behave more akin to a garbage collection service but I see that is very much not the case.

    With iphone resources being so scarce (and random at times) I suppose the best practice would be to keep the pool as small as possible and release as often as you can. In our app we've already experienced crashes at a memory usage of 20 megs on some iphones (browsers windows open, no reboot, etc..). I think we've actually spent more time optimizing to release objects sooner rather than actual leaking memory. I don't expect iphone users to reboot their phones even after installing an app so that memory crunch can hurt quick.

    ReplyDelete
  4. 成功不是一個海港,而是一次埋伏許多危險的旅程。 ..................................................

    ReplyDelete