What is memory management? Memory management is how an app manages its dynamically allocated memory. At a fundamental level, memory management is a simple concept:
1. Your app’s process is allotted a range of memory locations (bytes), called its
logical address space.
2. The bulk of those locations are placed into a vast pool of available memory called the heap.
3. Whenever your app needs a block of memory to store something (like an object) it requests a block of bytes from the heap.
4. A block of free bytes is found (allocated) and can now by utilized by your app. The pointer to this block of bytes becomes the object’s reference.
5. Your app uses the block/object to store values and do wonderful things.
6. When your app is done with the block/object, it passes the address back to the
heap. That range of locations is returned to the pool of free bytes, ready to be used for some other purpose.
It’s kindergarten rules: you take something off the shelf, play with it, and put it back when you’re done. As simple as it sounds, the operating system is already doing an incredible amount of work for you, keeping track of whatmemory is in use and efficiently recycling it.
Determining when your app needs a block of memory is easy. Figuring out when it should be returned to the heap keeps programmers up through the night. Giving blocks back in a timely fashion is important, whichbrings us to the first three pitfalls of memory management:
n Running out of memory
n Neglecting to return enough memory to the heap, and running out of memory
n Forgetting to return blocks you’re no longer using, and running out of memory
You’ll encounter the first problem when your app’s memory needs exceed that of the device. This
is a real possibility on iOS devices. Handheld computer systems have a tiny fraction of the memory available to typical desktop computers, and they don’t employ technology (like virtual memory stores) to extend that. So while it’s practically impossible for modern desktop applications to run out of addressable memory, it’s a daily occurrence for even the best iOS apps. If your app deals with large amounts of data, it’s something you’ll have to contendwith.
The second problem is just poor app design. Failing to return memory that you don’t need will mean your app uses more memory than it should. This reduces the performance of your app, other apps, and the iOS device in general. In Chapter 23, you’re going to analyze your app’s memory usage and take steps to release objects you don’t really need.
The last problem is called a memory leak, and that’s a bug. If you allocate a block of memory, forget to return it, and forget its address, that block of memory is lost—it stays allocated, and it can never (ever!) be used again.Repeat this a few thousand times, and your app will begin to suffer, eventually running out of free memory, and dying.
Your Grandfather’s Memory Management
Simple memory management follows the steps outlined at the beginning of “Memory Management;” your code allocates a block of memory when it wants one, and frees it again when it’s finished with it. You can do this directly,should you need to. Pass the number of bytes you want to allocate to the alloc function. It will return a pointer to an allocated block of memory. When you have finished using that memory, pass the same pointer to the freefunction, returning it to the pool of available memory.
When you allocate a new Objective-C object ([[MyClass alloc] init]), that +alloc class method eventually ends up calling alloc (the C function) to carve out a little chunk of the heap for your new object. When the object getsdestroyed, free is called to recycle it.
That sounds simple. So why isn’t this chapter ending already? When code allocates an object, some other code has to take responsibility for destroying it when it’s no longer needed. Specifically, you have to answer the generalquestion “what code is responsible for destroying the object when your app is done with it?” Answering that question can be a little tricky.
We’ve already talked about how objects form graphs. Objects refer to other objects, which refer to even more objects. Several objects can refer to the same object. An object can refer to an object that refers back to theoriginal object, creating a loop. In these situations, answering the question “who’s responsible for this object” isn’t easy, or even meaningful.
Here’s a simple example. In the last chapter, you saw how to use an attributed string to display styled text in a UIView.
Figure 21-1 shows a similar graph of objects.
It has two buttons, with different titles, but both titlesuse the same font.
Figure 21-1. Attributed string object graph
Now pretend the attributed string of a button is replaced with a different one.The graph of objects now lookslike the one in Figure 21-2.
Figure 21-2. Object graph with leaked objects
There’s no longer a reference to the original attributed string. Unless the attributed string, string, and
dictionary objects are destroyed, they’ll “leak,” wasting precious memory. It won’t take long before thousands(millions!) ofleaked objects choke your app to death.
You might suggest that the button take responsibility for destroying the attributed string object. That’s
an excellent suggestion. You could make a general rule that the object with the reference destroys the original objectwhen it’s done with it.
But which object should destroy the font object? Several objects reference it. No one object is “responsible” for the font object. So when the dictionary that refers to it is destroyed, does it, or does it not, need to destroythe font object?
And this is the central problem with object reclamation in an undirected graph of objects—determining which objects are still useful and which ones aren’t. Computer scientists and engineers have spent decades trying to solvethis problem, and they’ve come up with two solutions: garbage collection and reference counting.
Garbage Collection
iOS doesn’t use garbage collection (although it made a brief appearance in OS X’s Cocoa). The poster children for garbage collection are currently Java and C#. Understanding how garbage collection works is useful, mostly,as a conceptual basis for what automatic memory management does for you. You can then appreciate the logic behind reference counting when we get to that.
To that end, here’s the Cliff Notes version of garbage collection.
In garbage collection, the operating system keeps track of every object your app creates. It periodically traces the graph of objects in your app. It starts with your permanent objects (like your application object), and finds all ofthe objects it references (your view controllers), all of the objects those objects reference (your data model), and so on, until it’s created the set of reachable objects. These are all of the objects your app has access to. (If you haveno way of referring to an object, you can’t use it, no matter how much you want to.) What’s left over are the unreachable objects; in other words, the garbage. This is shown in Figure 21-3.
Figure 21-3. Reachable and unreachable objects
The attributed string, and the dictionary and string objects it refers to, have become unreachable.
These objects are destroyed and their memory recycled.
The beauty of garbage collection is that it takes almost no effort on the part of the programmer. You simply set, or forget, object references as needed. The operating system will eventually come around to figure out whichobjects you’re using and which ones you’re not.
That sounds fantastic, doesn’t it? So why doesn’t iOS use garbage collection? There are a couple of
reasons. Garbage collection shifts all of the work onto the operating system. Periodically sifting through
tens of thousandsof objects to determine who references what is not a trivial task. This requires a significant amount of CPU time, which translates into battery drain and intermittent unresponsiveness.
Secondly, research has concluded that garbage collection is only truly efficient when there is substantially more memory available than the application uses.1 When memory is tight, the performance of garbage collectionplummets, further reducing your app’s performance.
Garbage collection works great, therefore, when there’s lots of CPU capacity, no reason to conserve that capacity, and a surfeit of memory. What does that sound like? I hope you said “desktop computers” or “servers,” because that’s not the description of tiny handheld computing platforms, like iOS devices.
What you want is a solution like garbage collection—that can automatically determine what objects are in use and which ones need to be destroyed—without so much overhead. That solution is called reference counting.
Reference Counting
Reference counting counts the references to an object to determine if it’s still in use. It’s a cooperative system, whereby each object remembers the number of references to it—called its reference count. Objects that use anobject inform it when they establish a reference to it, and again later when they no longer need it. An object is destroyed when the last referring object breaks its connection. Here’s the scheme, in brief:
n When an object wants to maintain a reference to another object, it sends that object a -retain message. The -retain message increases the receiver’s reference count by one.
n When an object stops referring to an object, it first sends the object a -release
message. The -release message decrements the receiver’s reference count by one.
n When an object’s reference count becomes 0, it destroys itself.
In essence, it shifts the responsibility for determining when an object is no longer being used from the operating system to the object and the objects that refer to that object.
1Recent studies conclude that garbage collection matches the efficiency of reference counting only
when the total heap memory is at least 4 to 5 times that of the application’s memory footprint: Quantifying the Performance of Garbage Collection vs. Explicit Memory Management by Matthew Hertz and Emery D. Berger, Dept.of Computer Science, University of Massachusetts Amherst, Amherst, MA.
Returning to the attributed string example, each object reference in the graph is accompanied by a
retain message. Each message increments the object’s reference count by one, and it knows that one more object is using it. Shared objects, like the Font object, have a reference count of two or more, as they’re being used by more than one object, as shown in Figure 21-4.
Figure 21-4. Reference counted objects
The sending of retain and release messages is typically encapsulated in the property setter method.
The setter method sends a retain message to the object reference being set, and a release message to the reference beingdisplaced. When you set a different attributed string object for the button object, the first thing that happens is a new retain message is sent to the new attributed string object, as shown in Figure 21-5. The attributed stringobject has, already, sent -retain messages to the string and dictionary it refers to, and the dictionary has already sent -retain messages to the string and font objects in its collection.
Figure 21-5. Retaining a new property
Before the button’s setter method returns, it sends a -release message to the object it was previously referencing. That object had a reference count of 1.
The -release message drops the object’s reference count to 0 and itimmediately destroys itself.
As part of its destruction, it sends-release messages to all of the objects it still has references to.
This begins a cascade of release messages that systematically seek out and destroy all objects that no
longer have an active reference, as shown in Figure 21-6.
Figure 21-6. Releasing old objects
Reference counting gives you most of the benefits of garbage collection, with less than one-third of the calories. All iOS apps use reference counting. When you start a new project you’ll be using Automatic Reference Counting(ARC). Existing projects may use ARC or manual reference counting (also called managed memory, or just reference counting). The decision changes how your write your code. Let’s start with old-fashioned reference counting.
Manual Reference Counting
When writing iOS apps using traditional reference counting, your code is responsible for retaining and releasing your object references as you change them. If you’re looking at non-ARC Objective-C code, it will be peppered with-retain, -release, and -autorelease messages. (I’ll explain
-autorelease shortly.) But it isn’t as hard as it sounds. Here are the basics:
n Send a -retain message to an object you want to continue referencing it.
n For an object you retained, send a -release message to it immediately before discarding the reference.
n Every new (just created) object has a reference count of 1. If you create an object (with [[SomeClass alloc] init] or -copy), you’ve already retained it and must release it when you’re finished with it.
n When an object is destroyed, it receives a -dealloc message. The -dealloc
method must release all of the objects it still references.
You typically send your retain and release messages in your property setting methods—yet another good reason to use setter methods for changing property values. Now can you appreciate what the retain attribute is for? Thesetter method for a retain property promises to retain the new object and release the old one. A typical property and setter pair look like this:
@property (retain) UIFont *font;
...
- (void)setFont:(UIFont*)font
{
[font retain]; [_font release];
_font = font;
}
Even better, if you let Objective-C synthesize your setter method for you, it will send all of the correct messages.
Your object is also responsible for releasing any references it still has when it’s destroyed. Every object receives a -dealloc method, immediately after its reference count reaches 0 and just before its memory is returned to theheap. Here’s a typical -dealloc method:
- (void)dealloc
{
[_font release]; [super dealloc];
}
That wasn’t too complicated, and it works pretty well—except for two gigantic flaws. Well, one gigantic flaw and a serious problem. Let’s look at the gigantic flaw first.
Jumping into the Pool
This reference counting thing works great for about five minutes, until you need to create and return an object from a method. Think about it. If the code that creates an object is responsible for releasing it when it’s done, thatmeans it’s impossible to create an object and return it to the sender. You end up with code like this:
- (NSArray*)allThatJazz
{
NSMutableArray *jazz = [[NSMutableArray alloc] init]; [jazz addObject:hands];
[jazz release];
return jazz; // oops, jazz no longer exists!
}
You might be tempted to suggest that allThatJazz return a retained object, and make the sender
responsible for releasing it, but that creates a royal mess. Now every sender has to know which
messages return objectsthey have to release and which ones don't. And forget about changing your mind and laterdeciding that allThatJazz should now return an object that doesn’t need to be released, because
then you’d have to change allthe code that uses it. No, there’s a better way.
The solution is a clever construct called the autorelease pool. The autorelease pool defers sending
-release messages to objects. After your code is finished, the autorelease pool is drained (yes,
that’s the actual term). It sends -release messages to all of the objects in the pool. Objects that no longer have any references are destroyed.
Use the autorelease pool to schedule a -release message to be sent to an object “at some point in the future.” You do this by sending the object an -autorelease message instead of a-release message.If a -release message means “I’m completely done with this object,” then the -autorelease message means “I will be done with this object, after this code is finished.”
Using the autorelease pool, the -allThatJazz method now looks like this:
- (NSArray*)allThatJazz
{
NSMutableArray *jazz = [[[NSMutableArray alloc] init] autorelease]; [jazz addObject:hands];
return jazz;
}
The jazz object is called an autoreleased object. The object has a non-zero reference count and is guaranteed to exist until the autorelease pool is drained. Even better, the object that sent the message
doesn’t have toworry about it either:
- (void)fosse
{
NSArray *jazz = [self allThatJazz]; if (jazz.count<3)
[self yell:@"More!"];
}
Notice that this code doesn’t retain or release the object, although it’s clearly “using” it. That’s because of this very important rule: Every Objective‑C method returns an object that’s retained by another object.
The objectwill be retained either by the object you requested it from or the autorelease pool.
What that means is that you don’t have to retain an object to use it immediately. In fact, you don’t have to
retain an object unless you want to continue using that object after your method returns. So as long as the -fossemethod doesn’t need to use jazz after it returns, it doesn’t have to retain or release it.
And what if it did need to use it later? Then it would send the object a -retain message and store it in a more permanent variable. That’s usually a property, whose setter method will send the -retain message automatically:
- (void)fosse
{
NSArray *jazz = [self allThatJazz]; self.modernJazz = jazz;
}
Are you still wondering when the autorelease pool is drained? Autorelease pools are created, and drained, by your app’s event loop. Back in Chapter 4, I explained how your app is “event driven.” Before each event isdispatched to your code, an autorelease pool is created. Every objectthat receives an -autorelease message is added to that pool. When all of your code finishes executing, and control returns
to the event loop, the autorelease pool is drained, destroying all the temporary objects. Beforethe next
event is dispatched, a new autorelease pool is created, and the process repeats.
Autorelease pools solve the huge flaw in reference counting, and generally means that most of your code doesn’t have to send any -retain or -release messages. I’ll get to the other big problem shortly, but first let’s review whatyou’ve learned so far.
Quick Summary
That’s 99% of manual memory management. Here’s a simplified summary of how to use it:
n After creating an object ([[Class alloc] init] or -copy), either:
n Use it and then send it a -release message
n Send it an -autorelease message
n An object returned by a message can be safely used for the duration of the method.
n If you store an object reference anywhere where it could be used after the method returns, it should be retained (normally handled by a property setter method).
n If your code sent an object a –retain message, you must send it a matching
-release or -autorelease message before discarding the reference to it (normally handled by a property setter method or by -dealloc).
With these basic rules, and some help from your setter methods, object destruction and memory management is something that happens, more or less, in the background. You get most of the benefits of garbage collectionwith almost none of the costs. There is, however, one situation where garbage collection still beats reference counting. Let’s take a look at that problem next.
OBSCURE REFERENCE COUNTING PITFALLS
There are a few, oddball, situations where following these simplified rules can still get you into trouble.
Consider this code:
id obj = [array objectAtIndex:0]; [array removeObjectAtIndex:0]; [obj doSomething]; // ! crash !
So what’s wrong? The problem is that the code is relying on the -objectAtIndex: message to return either a retained (by array) or autoreleased object. It does that. The object is, in fact, retained by array. But the next lineremoves the object from the array. The array is no longer retaining the object. If this was the object’s only reference, it destroys itself. The next lineof code blows up your app, because obj now points to garbage. (Note:Automatic Reference Counting is
not fooled by this kind of code.)
You’ll sometimes see the addition of reference counting messages to work around problems like this:
id obj = [[[array objectAtIndex:0] retain] autorelease]; [array removeObjectAtIndex:0];
[obj doSomething]; // safe as houses
A complete set of memory management rules can be found in the Advanced Memory Management Programming Guide, which you can find in Xcode’s Documentation and API Reference window.
Breaking the Cycle
Now let’s talk about “the other problem” with reference counting. An object can, indirectly, refer to itself. This causes its reference count to be artificially high, preventing it from being released. Take the simple
example of aclassroom data model, shown in Figure 21-7.
Figure 21-7. Circular retains
A Class object creates a Teacher object and adds Student objects to it. The Teacher maintains a reference to each student, which necessitates sending each a retain message. Each Student needs a connection to its Teacher. Thatconnection is a property that references and retains the Teacher object.
When class is over, the Teacher object is released. What happens next is, well, nothing. As you see in Figure 21-8, the Teacher’s reference count drops from 4 to 3, and it just sits there, thinking that
it has three other objects that are still using it. And there are, but those three objects are the three
Student objects it’s retaining.
Figure 21-8. Releasing an over-retained Teacher
This is called the circular retain problem, and reference counting can’t fix it. The objects in the circle are leaked and can never be destroyed.
The solution is to not retain all of the object references.
In Figure 21-9, the Student objects have been modified so they do not send -retain messages to the Teacher object. Now when the Teacher is released, its reference countwill drop to 0. It will destroy itself and its three Student objects.
Figure 21-9. Unretained object references
The Student objects are using unretained references. Unretained references are used in parent-child relationships (like the Teacher-Student example), delegates, and sometimes target objects to avoid creating circular retains. Tocreate an unretained property, use the assign attribute (@property (assign) id delegate). An assign property simply stores the object reference; it doesn't send any retain or release messages.
This fixes the circular retain problem, but I have to warn you (and I can’t stress this enough), unretained references are dangerous. If the object you’re referencing gets destroyed—and it can, because you’re not retaining it—theobject reference will point to garbage memory. This is one of the most vexing bugs you’ll encounter in an Objective-C program.
The key is to write your code in a way that ensures an unretained reference won’t ever end up pointing to a destroyed object. The Teacher object, for example, could set the teacher property of each Student to nil beforereleasing them in its -dealloc method. If some other object was still retaining a Student object, it won’t have a teacher property pointing to garbage memory.
This is also why, throughout this book, I’ve repeatedly warned you to remove your object from the notification center before your object is destroyed. In several places, you’ve seen this code:
- (void)dealloc
{
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
The reason is because the notification center uses unretained references. (Don’t ask why; it’s a convoluted reason with a long history.) If you allow your object to be destroyed, but leave it registered with the notificationcenter, the next notification message will be sent to garbage. And trust me, that’s a hard bug to diagnose.
Scared Straight
Let’s wrap up this discussion with all of the ways reference counting and memory management can go horribly wrong. Oh, come on, it’ll be fun!
n Creating or retaining an object and then forgetting to send it a -release or
-autorelease message. This will create a memory leak. It’s called an
over‑retain bug.
n Erroneously sending an object a -release or -autorelease message. This causes the object to be prematurely destroyed while there are still references to it. The next message sent to the reference will behavebadly. This is called an over‑release bug.
n Creating circular retains. This eventually results in leaked objects.
n Failing to clear an unretained object reference once the object is destroyed. This is called a dangling pointer bug.
There are a hundred different scenarios that can lead to one of these bugs, but all of them fall into those
four categories.
Are you now too scared to write another line of Objective-C? Take a deep breath and relax, because Automatic Reference Counting is here to save you. ARC prevents three of these four tragedies, and gives you the tools to easilyavoid the fourth. Let’s now return to the safe confines of ARC.
Automatic Reference Counting
Automatic Reference Counting is a feature of the Objective-C compiler. It analyses your source code to determine exactly when you stop using an object reference, and automatically inserts code to send the correct -retain and -release messages. This consistency means your code is immune from half of the reference counting bugs: over-retain and over-release. They simply don’t happen.
The two remaining problems, avoiding circular retains and dangling pointers, is aided by a new weak qualifier. Any variable or property can be declared to be weak. It doesn’t retain the object it refers to (just like an unretainedreference), but it’s also safe. The Objective-C runtime automatically sets the variable to nil if the object it’s pointing to should be destroyed. So you can solve those annoying circular retention problems without worrying aboutdangling references that could crash your app.
ARC comes at a (slight) cost. There are certain programming practices that ARC doesn’t allow. But I’m surethat, after reviewing them, you’ll agree the benefits of ARC far outweigh the few obscure features you have togive up:
n Your code can’t send -retain, -release, or -autorelease messages. That’s the compiler’s job now.
n Your -dealloc method no longer sends [super dealloc] at the end. The compiler does that for you.
n You cannot put object pointers in a C struct (struct { int number; id obj }).
n You cannot convert object pointers into C type pointers (void *cPtr = obj), or vice versa.
n All automatic object pointer variables are initialized to nil.
There are some other, far more obscure, limitations, but those are the big ones. If you think about it, it’s
not much to give up. Most of what you have to abandon (sending your own -retain and
-release messages), is what you want ARC to save you from in the first place.









No comments:
Post a Comment