Wednesday, 7 May 2014

Memory Management [The Elephant in the Room]

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 apps 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 objects 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.

Its 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 apps 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 dont employ technology (like virtual memory stores) to extend that. So while its practically impossible for modern desktop applications to run out of addressable memory, its a daily occurrence for even the best iOS apps. If your app deals with large amounts of data, its something you’ll have to contendwith.

The second problem is just poor app design. Failing to return memory that you dont 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 apps memory usage and take steps to release objects you dont really need.
The last problem is called a memory leak, and thats 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 Grandfathers 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, anfrees it again when its finished with itYou 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 isnt this chapter ending already? When code allocates an object, some other code has to take responsibility for destroying it when its 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 “whos responsible for this object” isnt easy, or even meaningful.
Heres 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

Theres 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 wont 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. Thats
an excellent suggestion. You could make a general rule that the object with the reference destroys the original objectwhen its 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 arent. 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 doesnt use garbage collection (although it made a brief appearance in OS Xs 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, heres 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 its 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 cant use it, no matter how much you want to.) Whats 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, doesnt it? So why doesnt 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 apps performance.

Garbage collection works great, therefore, when theres 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 thats 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 its still in use. Its 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. Heres 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 receiverreference count by one.

n     When an object stops referring to an object, it first sends the object a -release
message. The -release message decrementthe receivers reference count by one.

n     When an objects 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 applications 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 objects 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 buttons setter method returns, it sends -release message to the object it wapreviously referencing. Thaobject had reference count of 1.
The -release message drops the objects 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. Lets 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 isnt 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 its destroyed. Every object receives a -dealloc method, immediately after its reference count reaches 0 and just before its memory is returned to theheap. Heres a typical -dealloc method:

-  (void)dealloc
{
[_font release]; [super dealloc];
}

That wasnt too complicated, and it works pretty well—except for two gigantic flaws. Well, one gigantic flaw and a serious problem. Lets 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 its done, thatmeans its impossible to create an object and return it to the senderYou 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 objectand 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 tchange allthe code that uses it. No, theres 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,
thats 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
doesnt have toworry about it either:

-  (void)fosse
{
NSArray *jazz = [self  allThatJazz]; if (jazz.count<3)
[self  yell:@"More!"];
}
Notice that this code doesnt retain or release the object, although its clearly “using” it. Thats because of this very important rule: Every Objective‑C method returns an object thats 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 dont have to retain an object to use it immediately. In fact, you donhave to
retain an object unless you want to continue using that object after your method returns. So as long as the -fossemethod doesnt need to use jazz after it returns, it doesnt 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. Thats 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, andrained, by your apps event loop. Back in Chapter 4, 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 doesnt have to send any -retain or -release messages. I’ll get to the other big problem shortly, but first lets review whatyou’ve learned so far.


Quick Summary

Thats 99% of manual memory management. Heres 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. Lets 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 yointo trouble.
Consider this code:

id obj  = [array objectAtIndex:0]; [array  removeObjectAtIndex:0]; [obj  doSomething]; // ! crash !

So whats 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 objects 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 Guidewhich you can find in Xcodes Documentation and API Reference window.

Breaking the Cycle

Now lets 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 tit. The Teacher maintains a referencto 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 Teache object.

When class is over, the Teacher  object is released. What happens next is, well, nothing. As you see in Figure 21-8, the Teachers 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 its retaining.


Figure 21-8. Releasing an over-retained Teacher

This is called the circular retain problem, and reference counting cant 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 cant 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 wont 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 wont 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. (Dont ask why; its 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, thats a hard bug to diagnose.


Scared Straight

Lets 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. Its called an
overretain 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 overrelease 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. Lets 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 dont 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 doesnt retain the object it refers to (just like an unretainedreference), but its also safe. The Objective-C runtime automatically sets the variable to nil if the object its 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 doesnt 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 cant send -retain-release, or -autorelease messages. Thats the compilers 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, its
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