3. Memory Management

Understanding memory management in the Cocoa Touch framework is one of the first major roadblocks for newcomers. Unlike Objective-C on the Mac, Objective-C on the iPhone has no garbage collector. Thus, it is your responsibility to clean up after yourself.

Memory Management Concepts

This book assumes you are coming from a C background, so the words “pointer,” “allocate,” and “deallocate” shouldn’t scare you. If your memory is a little fuzzy, here’s a review. The iPhone has a limited amount of random access memory. Random access memory (RAM) is much faster to write to and read from than a hard drive, so when an application is executing, all of the memory it consumes is taken from RAM. When an operating system like iPhone OS launches your application, it reserves a heaping pile of the system’s unused RAM for your application. Not-so-coincidentally, the memory your application has to work with is called the heap. The heap is your application’s playground; it can do whatever it wants to it, and it won’t affect the rest of the OS or any other applications.

When your application creates an instance of a class, it goes to the giant heap of memory it was given and takes a little scoop. Since you typically create objects during the course of your application’s execution, you start using more and more of the heap. Most objects are not permanent, and when an object is no longer needed, the memory it was consuming should be returned to the heap. This way, it can be reused for another object created later.

There are two major problems in managing memory:

image

Managing memory in C

In the C programming language, you have to explicitly ask the heap for a certain number of bytes. This is called allocation. It is the first stage of the heap life cycle shown in Figure 3.1. To do this, you use a function like malloc. If you want 100 bytes from that heap, you do something like this:

image

Figure 3.1. Heap allocation life cycle

image

You then have 100 bytes with which you can perform some task like writing a string to it and then printing that string (which would require reading from those bytes). The location of the first of those 100 bytes is stored in the pointer buffer. You access the 100 bytes by using this pointer.

When you don’t want to use those bytes anymore, you have to give them back to the heap by using the free function. This is called deallocation.

image

By calling free, those 100 bytes (starting at the address stored in buffer) are returned to the heap. If another malloc function is executed, any of these 100 bytes are fair game to be returned. Those bytes could be divvied up into smaller sections, or they could become part of a larger allocation. Because you don’t know what will happen with those bytes when they are returned to the heap, it isn’t safe to access them through the buffer pointer anymore.

Managing memory with objects

Even though at the base level an object is bytes allocated from the heap, you never explicitly call malloc or free with objects.

Every class knows how many bytes of memory it needs to allocate for an instance. When you create an instance of a class by sending it the alloc message, the correct number of bytes is allocated from the heap. Like with malloc, you are returned a pointer to this memory (Figure 3.2). However, when using Objective-C, we think in terms of objects rather than raw memory. While our pointers are still pointing to a spot in memory, we don’t need to know the details of that memory; we just know we have an object.

Figure 3.2. Allocating an object

image

Of course, once you allocate memory from the heap, you need a way to return that memory back to the heap. Every object implements the method dealloc. When an object receives this message, it returns its memory back to the heap.

So, malloc is replaced with the class method alloc, and the function free is replaced with the instance method dealloc. However, you never explicitly send a dealloc message to an object; an object is responsible for sending dealloc to itself. That begs the question: if an object is in charge of destroying itself, how can it know if other objects are relying on its existence? This is where reference counting comes into play.

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 its retain count (Figure 3.3).

Figure 3.3. Retain count for a dog

image

When an object is created — and therefore has one owner — its retain count is set to 1. When an object gains an owner, its retain count is incremented. When an object loses an owner, its retain count is decremented. When that retain count reaches 0, 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:

image

Simple, right? Now let’s consider how retain counts work between objects. If object A creates object B (through alloc and init), A must send B the message release at some point in the future. Releasing B doesn’t necessarily deallocate it; it is left to B to decide if it should be deallocated. (If B has another owner, it won’t destroy itself.)

If some other object C wants to keep B around, C becomes an owner of B by sending it the message retain. What reason does C have to keep B around? C wants to send B messages.

Let’s imagine 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 was given it. Therefore, the grocery list will still exist whenever he needs it, and your friend is now the sole owner of the list.

Here is your code:

image

Here is your friend’s code:

image

Retain counts can still go wrong in the two classic ways: leaks and premature deallocation. First, you could give the grocery list to your friend who retains it, but you don’t release it. Your friend finishes the shopping and releases the list. You have forgotten where it is, but because you never released it, it still exists. Nobody has this grocery list anymore, but it still exists because its retain count is greater than 0. This is a leak.

Think of the grocery list as an NSString. You have a pointer to this NSString in the method where you created it. If you leave the scope of the method without releasing the NSString, you’ll lose the pointer along with the ability to release the NSString later. Even if every other object releases the NSString, it will never be deallocated.

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 retain it. When you release it (thinking it was safe with your friend), it is deallocated because you were its only owner. When your friend attempts to use the list, he can’t find it because it doesn’t exist anymore.

When an object attempts to access another object that no longer exists, your application accesses bad memory, starts to fail, and eventually (although sooner is better than later for debugging) crashes.

If an object retains another object, that other object is guaranteed to exist. So correct use of retain counts avoids premature deallocation. Now let’s look more closely at memory leaks.

Avoiding memory leaks with autorelease

You already know that an object is responsible for returning its own bytes to the heap and that an object will do that when it has no owners. What happens when you want to create an object to give away, not to own? You own it by virtue of creating it, but you don’t have any use for it.

Let’s make this idea more concrete with an example from the RandomPossessions tool you wrote last chapter. In the Possession class, you implemented a convenience method called randomPossession that return an instance of Possession with random parameters. The owner of this instance is the class Possession (because the object was created inside of a Possession class method), but Possession is only creating it because another object wants it. The pointer to the Possession instance is lost when the scope of randomPossession runs out, but the object still has a retain count of 1.

Now, in your main function, you could release the instance returned to you by this method. But, you didn’t allocate the random possession in the main function. Therefore, releasing the memory isn’t main’s responsibility. Since the alloc message was sent to the Possession class inside randomPossession’s implementation, it is randomPossession’s responsibility to release the memory. But looking at the following block of code, where could you safely release it?

image

How can you avoid this memory leak? 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 the 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, that autorelease pool will release it, which will set its retain count back to 1. The return value for autorelease is the instance that is sent the message, so you can method chain autorelease.

// Because autorelease returns the object being autoreleased, we can do this:
NSObject *x = [[[NSObject alloc] init] autorelease];

Sometimes the idea of “the object will be released some time in the future” confuses developers. When an iPhone 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) and then processes that event by calling the methods you have written in your classes. Whenever an event occurs, it breaks from that loop and starts executing your code. 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.4. 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.4. Autorelease pool draining

image

Managing memory in accessors and properties

Accessors are methods that get and set instance variables. Getter methods don’t require any additional memory management:

image

Setters, however, need to take care to properly retain new values and release old ones.

image

Notice that if pet hasn’t been set, it is nil, and [pet release] would have no effect.

It is important to retain the new value before releasing the old one. Why? What if pet and d are pointers to the same object? What if that object has a retain count of 1? If you release it before you retain it, the retain count goes to 0, and the object is deallocated.

Here is the same thing in another style:

image

Once again, properties come to the rescue. If you use properties, all of the memory management code for your accessors is written for you when you synthesize the property. To have the compiler generate an accessor that properly releases and retains for you, you can use the retain attribute when declaring your properties in a header file:

@property (nonatomic, retain) Dog *pet;

Then, in the implementation file, synthesize the method:

@synthesize pet;

Retain count rules

Let’s make a few rules from these ideas:

• If you send the message alloc to a class, the instance returned has a retain count of 1, and you are responsible for releasing it.

• If you send the message copy (or mutableCopy) to an instance, the instance returned has a retain count of 1, and you are responsible for releasing it (just as if you had allocated it).

• Assume that an object created through any other means (like a convenience method) has a retain count of 1 and is marked for autorelease.

• If an object wants to keep another object around (and the keeper didn’t allocate it), it must send the wanted object the message retain.

• If an object no longer wants to keep another object around, it sends that object the message release.

There is one exception to the rules: in any method that starts with new, the object returned should be assumed to not be autoreleased.

Managing Memory in RandomPossessions

Now that you have the theory and some rules, you can implement better memory management in RandomPossessions. Open the RandomPossessions.xcodeproj file that you created in the last chapter. There are four memory management problems to fix in this project.

The first is found in the main function of RandomPossessions.m where you created an instance of NSMutableArray named items. You know two things about this instance: its owner is the main function and it has a retain count of one. It is then 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:

image

The object pointed to by items decrements its retain count when this line of code is executed. In this case, that object is deallocated because main was the only owner. If another object had retained items, it wouldn’t have been deallocated.

There is one more detail to take care of. The instance of NSMutableArray that items pointed to is now gone. However, items is still storing the address that was the instance’s location in memory. It is much safer to set the value of items to nil. Then any messages mistakenly sent to items will have no effect.

[items release];
items = nil;

The ordering of those two statements is important. Ordering them this way says, “Send the object release, and then clear my pointer to it.” What would happen if you swapped the order of these statements? It would be the same thing as saying, “Set my pointer to this object to nil and then send the message release to.... Oh, no! I don’t know where that object went!” You would leak the object: it wasn’t released before you erased your pointer to it.

The second memory problem occurs when you create an instance of NSMutableArray and fill it with instances of Possession returned from the randomPossession convenience method:

image

The implementation for randomPossession returns an instance of type Possession that it created by sending the message alloc. This object is owned by this class method and therefore has a retain count of 1.

When you add a Possession instance to an NSMutableArray, the array becomes an owner of that object, so its retain count is increased to 2. After randomPossession finishes executing, however, it loses its pointer to the Possession it created. The Possession instance still has two owners, but only one still has a pointer to it (items). Memory leak!

This is a perfect opportunity to use autorelease. The method randomPossession should send autorelease to an instance it creates and relinquish its ownership of that instance. The object will still exist temporarily and be retained when it is added to the NSMutableArray. The instance of NSMutableArray will then be the sole owner of this new Possession. In effect, you have transferred ownership of the instance from randomPossession to items. When the array deallocates itself and releases the objects it contains, each object will have a retain count of 0 and will deallocate itself. Memory leak solved.

Now fix the leak in the randomPossession method in Possession.m.

image

When working with an instance of NSMutableArray, three rules apply to object ownership:

• When an object is added to an NSMutableArray, that object gets 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 gets 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 as shown in Figure 3.5.

Figure 3.5. Deallocating an NSMutableArray

image

The third memory problem in RandomPossessions is in the description method that Possession implements. This method creates and returns an instance of NSString that needs to be autoreleased.

image

You can make this even simpler by using a convenience method. NSString (as well as many other classes in the iPhone SDK) includes convenience methods that return autoreleased objects. Update description to use the convenience method stringWithFormat: to ensure that the NSString instance that description creates will be autoreleased.

image

The final memory problem has to do with the instance variables within Possession objects.

When the retain count of a Possession instance hits zero, it will send itself the message dealloc. The dealloc method of Possession has been implemented by its superclass, NSObject, but NSObject knows nothing about the instance variables added to Possession. So you must override dealloc in Possession.m to release any instance variables that have been retained.

image

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, it should go up its class hierarchy and release any instance variables of its superclass. In the end, the implementation of dealloc in NSObject will return the object’s memory to the heap.

Now let’s check your understanding of memory management concepts by looking more closely at instance variables and memory management.

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.

Why do you need to release these instance variables in the first place? Where are the calls to alloc, retain, or copy that make an instance of Possession an owner of these objects?

Let’s start with the instance variable dateCreated. Because it is allocated in the designated initializer for Possession, that instance of Possession becomes an owner and needs to release the object pointed to by dateCreated according to the first of the retain count rules described on page 52.

To figure out the other two instance variables, possessionName and serialNumber, you have to go back to their property declarations in Possession.h.

@property (nonatomic, copy) NSString *possessionName;
@property (nonatomic, copy) NSString *serialNumber;

Both of the properties associated with these instance variables have the copy attribute. When the message setPossessionName: is sent to an instance of Possession, the incoming parameter is sent the message copy. The instance variable possessionName is then set to point at that copied instance. If you wrote the code for setSerialNumber: instead of using @synthesize, it would look something like this:

image

The second retain count rule states that, if an object copies something, the object becomes an owner of that thing. Therefore, the owning object needs to release the copied object in its dealloc method. The same would hold true of these instance variables if their property attribute was retain (but not if the attribute were assign, which is a simple pointer assignment).

Strings come in two flavors: NSString and NSMutableString. Because an NSString can never be changed, there is seldom a need to copy it. Thus, in the case of NSString (and most other immutable objects), the copy method looks like this:

image

This approach prevents unnecessary copying. For example, the code above is basically equivalent to this:

image

Note, however, that this code is not exactly equivalent. Because of some underlying implementation details, both NSString and NSMutableString might return YES if asked whether they are of type NSMutableString.

Sometimes in this book, we will show you example code that does not exactly match the implementation in the SDK. We do this to give you a better understanding of the concepts being discussed. We’re not lying to you; we’re just sparing you some of the details until you’re more comfortable with the concepts. Once you’re there, you can divine all the details from the documentation. (In fact, we already did this when we gave an example of implementing retain and release earlier in this chapter. The implementations of these methods are actually much dirtier.)

Congratulations! You’ve implemented retain counts and fixed four memory leaks. Your RandomPossessions application now manages its memory like a champ!

Keep this code around because you are going to use it in later chapters.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset