- Memory Management Basics
- Reference Counting
- For the More Curious: More on Memory Management
Reference Counting
In the Cocoa Touch framework, Apple has adopted manual reference counting to manage memory and avoid premature deallocation and memory leaks.
To understand reference counting, imagine a puppy. When the puppy is born, it has an owner. That owner later gets married, and the new spouse also becomes an owner of that dog. The dog is alive because they feed it. Later on, the couple gives the dog away. The new owner of the dog decides he doesn't like the dog and lets it know by kicking it out of the house. Having no owner, the dog runs away and, after a series of unfortunate events, ends up in doggy heaven.
What is the moral of this story? As long as the dog had an owner to care for it, it was fine. When it no longer had an owner, it ran away and ceased to exist. This is how reference counting works. When an object is created, it has an owner. Throughout its existence, it can have different owners, and it can have more than one owner at a time. When it has zero owners, it deallocates itself and goes to instance heaven.
Using retain counts
An object never knows who its owners are. It only knows how many it currently has. It keeps track of this number in its retain count (Figure 3.2).
Figure 3.2 Retain count for a dog
When an object is created – and therefore has one owner – its retain count is set to one. When an object gains an owner, it is sent the message retain, and its retain count is incremented. When an object loses an owner, it is sent the message release, and its retain count is decremented. When that retain count reaches zero, the object sends itself the message dealloc, which returns all of the memory it occupied to the heap.
Imagine how you would write the code to implement this scheme yourself:
- (id)retain { retainCount++; return self; } - (void)release { retainCount--; if (retainCount == 0) [self dealloc]; }
Let's consider how retain counts work between objects by imagining you have a grocery list. You created it, so you own it. Later, you give that grocery list to your friend to do the shopping. You don't need to keep the grocery list anymore, so you release it. Your friend is smart, so he retained the list as soon as he got it. Therefore, the grocery list will still exist whenever your friend needs it, and he is now the sole owner of the list.
Here is your code:
- (void)createAndGiveAwayTheGroceryList { // Create a list GroceryList *g = [[GroceryList alloc] init]; // (The retain count of g is 1) // Share it with your friend who retains it [smartFriend takeGroceryList:g]; // (The retain count of g is 2) // Give up ownership [g release]; // (The retain count of g is 1) // But we don't really care here, as this method's // responsibility is finished. }
Here is your friend's code:
- (void)takeGroceryList:(GroceryList *)x { // Take ownership [x retain]; // Hold onto a pointer to the object myList = x; }
Retain counts can still go wrong in the two classic ways: leaks and premature deallocation. Continuing with the grocery list example, say you create and give a grocery list to your friend. He retains it, but you don't release it. Your friend finishes the shopping and releases the list. By now, you've forgotten where the list is, and since you never released it, its retain count is greater than zero – and now always will be. At this moment, nobody knows where this list is, but it still exists. This is a leak.
Think of the grocery list as an NSArray. You have a pointer to this NSArray in the method where you created it. If you leave the scope of the method without releasing the NSArray, you'll lose the pointer along with the ability to release the NSArray later. (You can't send a release message unless you know where to send it.) Even if every other owner releases the NSArray, it will never be deallocated, and the application can't use that memory for something else.
Consider the other way this process can go wrong – premature deallocation. You create a grocery list and give it to a friend who doesn't bother to retain it. When you release the list, it is deallocated because you were its only owner. Later, when your friend tries to use the list, he can't find it because it doesn't exist anymore.
This situation is worse that it sounds. Not only is your friend unable to do the shopping, but there are also application-level consequences to premature deallocation. When an object attempts to access another object that doesn't exist, your application accesses bad memory, starts to fail, and eventually crashes. On the other hand, if an object retains the objects it needs, then those objects are guaranteed to exist, and this type of disaster is avoided.
Reference counting is all about responsibility: if something creates an object, it is an owner of that object. Same goes for retaining an existing object. Releasing an object relinquishes that ownership. If something takes ownership of an object, it is responsible for relinquishing its ownership when it can no longer send messages to that object – when it no longer has a pointer to that object.
Let's make these ideas more concrete with an example from the RandomPossessions tool you wrote in the last chapter. Open RandomPossessions.xcodeproj and then open main.m in the editor area. In the main function, you created an instance of NSMutableArray named items. You know two things about this instance: the main function owns it, and it has a retain count of one. As an owner, it is main's responsibility to send this instance the message release when it no longer needs it. The last time you reference items in this function is when you print out all of its entries, so you can release it after that:
for (Possession *item in items) { NSLog(@"%@", item); } [items release]; items = nil;
When the message release is sent, the object pointed to by items decrements its retain count. In this case, the object is deallocated because main was its only owner. If another object had retained items, it wouldn't have been deallocated.
Using autorelease
You created items in main, use it there, and release it there. But what happens when you want to create an object to give out, not to own and use yourself?
This is often the case with convenience methods – class methods that return instances of the class. In the Possession class, you implemented a convenience method called randomPossession that returns an instance of Possession with random parameters. The Possession class owns this instance because it was created inside of a Possession class method, and the instance has a retain count of one.
However, the Possession class itself has no use for this instance; randomPossession is called by main where it returns the newly created Possession instance.
Should you release the Possession in main?
for(int i = 0; i < 10; i++) { Possession *p = [Possession randomPossession]; [items addObject:p]; // Don't do this! [p release]; }
This is a very bad idea. The responsibility that is the core of reference counting includes not releasing objects that don't belong to you. That's like cancelling your friend's party. Or taking your neighbor's dog to the pound. If you don't own it, you shouldn't release it, and main does not own this Possession instance – it did not allocate it or retain it; it only has a pointer to it, courtesy of the randomPossession convenience method.
Releasing the possession is the responsibility of the Possession class, and it must be done in randomPossession before the pointer to it is lost when the scope of the method runs out. But where in randomPossession can you safely release the new Possession instance?
+ (id)randomPossession { ... Create random variables ... Possession *newPossession = [[self alloc] initWithPossessionName:randomName valueInDollars:randomValue serialNumber:randomSerialNumber]; // If we release newPossession here, // the object is deallocated before it is returned. return newPossession; // If we release newPossession here, this code is never executed. }
What can you do? You need some way of saying "Don't release this object yet, but I don't want to be an owner of it anymore." Fortunately, you can mark an object for future release by sending it the message autorelease. When an object is sent autorelease, it is not immediately released; instead, it is added to an instance of NSAutoreleasePool. This NSAutoreleasePool keeps track of all the objects that have been autoreleased. Periodically, the autorelease pool is drained; it sends the message release to the objects in the pool and then removes them.
An object marked for autorelease after its creation has two possible destinies: it can either continue its death march to deallocation or another object can retain it. If another object retains it, its retain count is now 2. (It is owned by the retaining object, and it has not yet been sent release by the autorelease pool.) Sometime in the future, the autorelease pool will release it, which will set its retain count back to 1.
Sometimes the idea of "the object will be released some time in the future" confuses developers. When an iOS application is running, there is a run loop that is continually cycling. This run loop checks for events, like a touch or a timer firing. Whenever an event occurs, the application breaks from the run loop and processes that event by calling the methods you have written in your classes. When your code is finished executing, the application returns to the loop. At the end of the loop, all autoreleased objects are sent the message release, as shown in Figure 3.3. So, while you are executing a method, which may call other methods, you can safely assume that an autoreleased object will not be released.
Figure 3.3 Autorelease pool draining
The return value for autorelease is the instance that is sent the message, so you can nest autorelease messages.
// Because autorelease returns the object being autoreleased, we can do this: NSObject *x = [[[NSObject alloc] init] autorelease];
At the end of randomPossession in Possession.m, autorelease the newly created instance so that the receiver of this object can choose to retain it or just let it be destroyed.
Possession *newPossession = [[self alloc] initWithPossessionName:randomName valueInDollars:randomValue serialNumber:randomSerialNumber]; return [newPossession autorelease]; }
Now, in main.m, when the main function asks the Possession class for a random possession, the class returns an autoreleased instance of Possession. At this point, nothing owns this instance. When the Possession is added to the items array, the array retains it, and it has a retain count of one.
for(int i = 0; i < 10; i++) { // Get a new Possession instance - no one owns it as it's been autoreleased Possession *p = [Possession randomPossession]; // Add p to the items array, it will retain that Possession [items addObject:p]; }
When will items release the Possession? When an NSMutableArray is deallocated, the objects it contains are released. Thus, when main (the sole owner of items) releases items, the NSMutableArray that items points to will release all its Possession instances (Figure 3.4). We've made sure these Possession instances had only items as an owner, so these instances will also be deallocated.
Figure 3.4 Deallocating an NSMutableArray
Here are three memory management facts to remember when working with instances of NSMutableArray:
- When an object is added to an NSMutableArray, that object is sent the message retain; the array becomes an owner of that object and has a pointer to it.
- When an object is removed from an NSMutableArray, that object is sent the message release; the array relinquishes ownership of that object and no longer has a pointer to it.
- When an NSMutableArray is deallocated, it sends the message release to all of its entries.
Now let's turn to another place in RandomPossessions where you should use autorelease. In Possession.m, you override the description method of Possession's superclass. This method creates and returns an instance of NSString. Change the description method so that it returns an autoreleased string.
- (NSString *)description { NSString *descriptionString = [[NSString alloc] initWithFormat:@"%@ (%@): Worth $%d, Recorded on %@", possessionName, serialNumber, valueInDollars, dateCreated]; return [descriptionString autorelease]; }
You can make this even simpler by using a convenience method. NSString, like many other classes in the iOS SDK, includes convenience methods that return autoreleased objects – just like randomPossession does now. Modify description to use the convenience method stringWithFormat:. This ensures that the NSString instance that description creates and returns is autoreleased.
- (NSString *)description { return [NSString stringWithFormat:@"%@ (%@): Worth $%d, Recorded on %@", possessionName, serialNumber, valueInDollars, dateCreated]; }
Accessors and memory management
Up to this point, our examples of ownership have been ownership by creation. When you want to own an object that you didn't create, you must retain it. For example, if an object has instance variables that point to other objects, that object should retain them. You can retain an object pointed to by an instance variable in the setter method for that variable.
Let's look at the instance variables of the Possession. Every instance of Possession has three instance variables that are pointers to other objects (possessionName, serialNumber, and dateCreated). Right now, the setter methods in Possession simply assign the incoming value to the instance variable:
- (void)setPossessionName:(NSString *)str { possessionName = str; }
Not good enough. If we give an NSString to a Possession for its possessionName, and the Possession doesn't retain it, then when we release the string, it will be destroyed. This is premature deallocation because the Possession still needs that string as its possessionName. The Possession will eventually send messages to or give out its possessionName. It will be embarrassing (and application-crashing) if the object that the variable points to doesn't exist.
Therefore, in a setter method, an object should retain the objects pointed to by its instance variables to make sure the objects continue to exist. Open Possession.m in the editor area. Modify the method setPossessionName: so that the Possession retains the string passed to it:
- (void)setPossessionName:(NSString *)str { [str retain]; possessionName = str; }
The Possession will increment the retain count of the string passed to it, and no matter what happens to that string elsewhere in code, it will still exist until the Possession releases it.
Now let's look at what happens if you use the setter method to change the possessionName. For example, imagine we named a possession "White Sofa," and then a keen friend points out that it is actually off-white:
Possession *p = [[Possession alloc] init]; [p setPossessionName:@"White Sofa"]; // Wait, no it isn't... [p setPossessionName:@"Off-white Sofa"];
Stepping through this code, p retains the string "White Sofa" and sets its possessionName to point at that string. Then, it retains the string "Off-white Sofa" and sets its possessionName to point at that string instead. This is a leak: the Possession lost its pointer to the "White Sofa" string but never released its ownership of it (Figure 3.5).
Figure 3.5 Sending a setter message more than once
Therefore, in a setter method, you retain the new object, release the object you currently have, and then make the assignment. Add the following line of code to this setter method in Possession.m.
- (void)setPossessionName:(NSString *)str { [str retain]; [possessionName release]; possessionName = str; }
You must retain the new object before releasing the current one. More often than you might imagine, possessionName and str will point at the same object. If you reverse the retain and release statements, you would release the object that you had planned to retain as the possessionName. Oops.
Now write the matching code for the method setSerialNumber: in Possession.m.
- (void)setSerialNumber:(NSString *)str { [str retain]; [serialNumber release]; serialNumber = str; }
What happens in these setter methods if the incoming argument or the current instance variable points to nil? If you pass nil as an argument in setPossessionName:, the Possession releases its current possessionName and sets its possessionName to nil. The result is the Possession has no possessionName. If you send setPossessionName: to a Possession that has no possessionName, you will send the release message to nil, which has no effect.
Note that we've only been changing setter methods. Getter methods do not need additional memory management. However, the object that sends the getter message may need to retain what is returned.
What about the other two instance variables? The dateCreated instance variable does not have a setter method; it is created and given its value in the designated initializer. Therefore, the instance of Possession already owns it, and it is ensured to exist. The valueInDollars instance variable needs no memory management because valueInDollars is a primitive and not an object.
Implementing dealloc
In the previous section, you released the object pointed to by possessionName when changing the name of a Possession. It is just as important to release the objects pointed to by a Possession's instance variables when the Possession is being deallocated.
When the retain count of a Possession instance hits zero, it will send itself the message dealloc. When the Possession instance is destroyed, its instance variables that are pointers to other objects are also destroyed (but not the objects they point to). Thus, you must ask yourself if the Possession owns these objects, and if it does, you must release them before these pointers are destroyed.
You own the objects pointed to by possessionName and serialNumber by virtue of retaining them in their setter methods. You also own dateCreated because you allocated it in the designated initializer for Possession.
Having established that you own these objects, you must release them before you lose your pointers to them. You can do this at the beginning of the dealloc method of Possession. In Possession.m, override dealloc to release the instance variables that the Possession owns.
- (void)dealloc { [possessionName release]; [serialNumber release]; [dateCreated release]; [super dealloc]; }
Always call the superclass implementation of dealloc at the end of the method. When an object is deallocated, it should release all of its own instance variables first. Then, because you call the superclass's implementation, it goes up its class hierarchy and releases any instance variables of its superclass. In the end, the implementation of dealloc in NSObject returns the object's memory to the heap.
Why send release to instance variables and not dealloc? One object should never send dealloc to another. Always use release and let the object check its own retain count and decide whether to send itself dealloc.
Simplifying accessors with properties
Now that you've added memory management to your setter methods, let's look at a shortcut for creating accessor (both setter and getter) methods called properties. A property declares accessors for you in a header file. In Possession.h, replace the accessor declarations with properties.
@interface Possession : NSObject { NSString *possessionName; NSString *serialNumber; int valueInDollars; NSDate *dateCreated; } + (id)randomPossession; - (id)initWithPossessionName:(NSString *)name valueInDollars:(int)value serialNumber:(NSString *)sNumber; - (id)initWithPossessionName:(NSString *)name; @property NSString *possessionName; @property NSString *serialNumber; @property int valueInDollars; @property NSDate *dateCreated; @end
Notice that properties are declared in the method area and not in the curly brackets with the instance variables. They also are, by convention, declared after class methods and initializers but before other instance methods.
Properties replace the accessor declarations, which saves a few lines of typing in the header file. But that's not all. You can also use properties to automatically generate the implementations of the accessor methods. You generate the accessors by synthesizing the property in the implementation file. But before we get to the actual synthesizing, we need to talk about property attributes.
Every property has a set of attributes that tailors the accessor methods it can generate. These attributes are listed in the property declaration. There are three categories of attributes:
atomicity |
We will always use nonatomic for this attribute. There is rarely a reason to use the default, atomic, and the discussion of why is outside the scope of this book. |
writability |
By default, a property is readwrite. A readwrite property will generate a setter and getter method. The other option, readonly, only generates a getter method. |
memory management |
This attribute category only applies to the setter method. By default, a property is assign. In this case, a property's setter method only assigns the incoming argument to its instance variable. The other options are retain and copy, where the incoming argument is either retained or copied and then assigned to the instance variable. (We'll talk more about copy in a moment.) |
In Possession.h, add attributes to the property declarations to match the current accessor implementations.
// The generated accessor methods for this property will be a getter // and a setter that retains the incoming object and releases the old object. @property (nonatomic, retain) NSString *possessionName; // Ditto to the previous property @property (nonatomic, retain) NSString *serialNumber; // The generated accessor methods for this property will be a getter and a setter // that simply assigns the incoming value to the ivar valueInDollars. @property (nonatomic) int valueInDollars; // The only generated accessor method for this property will be a getter. @property (nonatomic, readonly) NSDate *dateCreated;
Now we can synthesize the properties in the implementation file. In Possession.m, remove all of the accessor method implementations and synthesize the properties instead.
@implementation Possession @synthesize possessionName, serialNumber, valueInDollars, dateCreated;
Build and run the application. Everything should work the same as before.
Let's review what's changed here. Before, you explicitly declared and implemented all of your accessor methods. Now you've replaced the accessor declarations with property declarations and the accessor implementations with an @synthesize statement. The Possession keeps the same behavior with significantly less typing. In programming, whenever you can specify details and let the system do the work, it not only saves you typing, but it also helps prevent typos and other errors.
(So why did we make you type in all the accessors first instead of going straight to properties? It's important to understand what properties actually do. Too many new developers use properties without understanding the code behind them, and it trips them up later.)
There are a couple of additional points to make about properties. First, you do not have to synthesize a property. You can declare a property in the header file and implement the accessor methods yourself. This is useful for situations where you want to customize the accessors. You can also synthesize a property and implement one of the accessor methods; this overrides the method you replaced without affecting its partner.
Second, the name of a property does not have to match the name of the instance variable. In a synthesize statement, you can point a property at an instance variable of another name.
// This is just an example, don't type this code in. @interface Possession : NSObject { ... } @property (nonatomic, assign) NSString *name; @end @implementation Possession @synthesize name = possessionName; // This is equivalent to // - (void)setName:(NSString *)str // { // possessionName = str; // } // - (NSString *)name // { // return possessionName; // } @end
This links the name property to the possessionName instance variable; therefore, when sending the message name to an instance of Possession, the value of possessionName is returned.
Finally, you don't even need an instance variable at all. When you synthesize a property, the compiler looks for an instance variable of the same name and if it finds one, it uses that instance variable. If a matching instance variable is not found, one is automatically created for you.
copy and mutableCopy
There are times when instead of retaining an object, you want to copy an object. When you send the message copy to an instance, a brand new instance is created that has the same values as the original instance. Copying an object gives you a brand new object with a retain count of one, and the retain count of the original object is unchanged. The object that sent the copy message is the owner of the new object.
You typically want to make a copy of an object if it is mutable. For example, an NSMutableArray is a mutable array. There is also an NSMutableString, a mutable subclass of NSString. Since NSMutableString "is a" NSString, it is conceivable that you could have an instance of NSMutableString as the possessionName of a Possession.
Imagine what would happen if an NSMutableString was set as the possessionName of a Possession. Another object that had a pointer to this string could change it, which would also change the name of the Possession. The Possession would have no idea this had happened.
NSMutableString *str = [[NSMutableString alloc] initWithString:@"White Sofa"]; // This is okay, as NSMutableString is a NSString since it is a subclass [possession setPossessionName:str]; [str appendString:@" - Stained"]; // possession's name is now "White Sofa - Stained"
Typically, you do not want this behavior. Changing an object's instance variables without using an accessor is usually bad form. You can use copy to prevent the possibility of instance variables being changed behind your back.
In general, if a class has a mutable subclass, properties that are of the type of that class should have the attribute copy instead of retain. Then you will have a copy of the object that is all yours.
In Possession.h, change the memory management attribute of the two NSString properties to copy.
@property (nonatomic, copy) NSString *possessionName; @property (nonatomic, copy) NSString *serialNumber;
The generated setter methods for these properties now look like this:
- (void)setPossessionName:(NSString *)str { id t = [str copy]; [possessionName release]; possessionName = t; }
When you copy an object, the copy returned is immutable. For instance, if you copy an NSMutableArray, the new object is simply an NSArray. (If you want a copy that can change, you must send the message mutableCopy instead.) Keep in mind, not all classes have mutable subclasses, and not all objects can be copied.
Congratulations! You've implemented retain counts and fixed the memory management problems in RandomPossessions. Your application now manages its memory like a champ!
Keep this code around because you are going to use it in later chapters.
Retain count rules
Let's make a few rules about retain counts to carry with us. In these rules, we use the word "you" to mean "an instance of whatever class you are currently working on." It is a useful form of empathy: you imagine that you are the object you are writing. So, for example, "If you retain the string, it will not be deallocated." really means "If an instance of the class that you are currently working on retains the string it will not be deallocated."
Here, then, are the rules. (Implementation details are in parentheses.)
- If you create an object using a method whose name starts with alloc or new or contains copy, then you have taken ownership of it. (That is, assume that the new object has a retain count of 1 and is not in the autorelease pool.) You have a responsibility to release the object when you no longer need it. Here are some of the common methods that convey ownership: alloc (which is always followed by an init method), copy, and mutableCopy.
- An object created through any other means – like a convenience method – is not owned by you. (That is, assume it has a retain count of one and is already in the autorelease pool, and thus doomed unless it is retained before the autorelease pool is drained.)
- If you don't own an object and you want to ensure its continued existence, take ownership by sending it the message retain. (This increments the retain count.)
- When you own an object and no longer need it, send it the message release or autorelease. (release decrements the retain count immediately. autorelease causes the message release to get sent when the autorelease pool is drained.)
- As long as an object has at least one owner, it will continue to exist. (When its retain count goes to zero, it is sent the message dealloc.)
One of the tricks to understanding memory management is to think locally. The Possession class does not need to know anything about other objects that also care about its possessionName or serialNumber. As long as a Possession instance retains objects it wants to keep, you won't have any problems. Programmers new the language sometimes make the mistake of trying to keep tabs on objects throughout an application. Don't do this. If you follow these rules and always think local to a class, you never have to worry what the rest of an application is doing with an object.
Earlier in this chapter, we made changes to the accessor methods of Possession so they would properly handle memory management. Before that, the application ran perfectly fine. Why? The objects that were created and set as the instance variables of the Possession instances were never released. Therefore, it didn't matter if we retained them. However, in a real application, there are many more moving parts, and objects will be created and released. With proper memory management in place, this Possession class will now stand up in a real-world application.