iOS Programming: The Big Nerd Ranch Guide, 3/e (Big Nerd Ranch Guides) (16 page)

Read iOS Programming: The Big Nerd Ranch Guide, 3/e (Big Nerd Ranch Guides) Online

Authors: Aaron Hillegass,Joe Conway

Tags: #COM051370, #Big Nerd Ranch Guides, #iPhone / iPad Programming

BOOK: iOS Programming: The Big Nerd Ranch Guide, 3/e (Big Nerd Ranch Guides)
9.83Mb size Format: txt, pdf, ePub
Memory Management

If heap memory were infinite, we could create all the objects we needed and have them exist for the entire run of the application. But an application gets only so much heap memory, and memory on an iOS device is especially limited. So it is important to destroy objects that are no longer needed to free up and reuse heap memory. On the other hand, it is critical
not
to destroy objects that
are
still needed.

 

The idea of object ownership helps us determine whether an object should be destroyed.

 
  • An object with no owners should be destroyed.
    An ownerless object cannot be sent messages and is isolated and useless to the application. Keeping it around wastes precious memory. This is called a
    memory leak
    .
 
  • An object with at least one owner must not be destroyed.
    If an object is destroyed but another object or method still has a pointer to it (or, more accurately, a pointer to where it used to live), then you have a very dangerous situation: sending a message to an object that no longer exists will crash your application. This is called
    premature deallocation
    .
 
Using ARC for memory management

The good news is that you don’t need to keep track of who owns whom and what pointers still exist. Instead, your application’s memory management is handled for you by
automatic reference counting
, or ARC.

 

In both projects you’ve built in
Xcode
so far, you’ve made sure to
Use Automatic Reference Counting
when creating the project (
Figure 3.4
). This won’t change; all of your projects in this book will use ARC for managing your application’s memory.

 

Figure 3.4  Naming a new project

 

(If the
Use Automatic Reference Counting
box in
Figure 3.4
was unchecked, the application would use
manual reference counting
instead, which was the only type of memory management available before iOS 5. For more information about manual reference counting and
retain
and
release
messages, see the For the More Curious section at the end of this chapter.)

 

ARC can be relied on to manage your application’s memory automatically for the most part, but it’s important to understand the concepts behind it to know how to step in when you need to. So let’s return to the idea of object ownership.

 
How objects lose owners

We know that an object is safe to destroy – and should be destroyed – when it no longer has any owners. So how does an object lose an owner?

 
  • A variable that points to the object is changed to point to another object.
 
  • A variable that points to the object is set to
    nil
    .
 
  • A variable that points to the object is itself destroyed.
 

Let’s take a look at each of these situations.

 

Why might a pointer change the object it points to? Imagine a
BNRItem
. The
NSString
that its
itemName
instance variable points to reads

Rusty Spork.

If we polished the rust off of that spork, it would become a shiny spork, and we’d want to change the
itemName
to point at a different
NSString
(
Figure 3.5
).

 

Figure 3.5  Changing a pointer

 

When the value of
itemName
changes from the address of the

Rusty Spork

string to the address of the

Shiny Spork

string, the

Rusty Spork

string loses an owner.

 

Why would you set a pointer to
nil
? Remember that setting a pointer to
nil
represents the absence of an object. For example, say you have a
BNRItem
that represents a television. Then, someone scratches off the television’s serial number. You would then set its
serialNumber
instance variable to
nil
. The
NSString
that
serialNumber
used to point to loses an owner.

 

When a pointer variable itself is destroyed, the object that the variable was pointing at loses an owner. At what point a pointer variable will get destroyed depends on whether it is a local variable or an instance variable.

 

Recall that instance variables live in the heap as part of an object. When an object gets destroyed, its instance variables are also destroyed, and any object that was pointed to by one of those instance variables loses an owner.

 

Local variables live in the method’s frame. When a method finishes executing and its frame is popped off the stack, any object that was pointed to by one of these local variables loses an owner.

 

There is one more important way an object can lose an owner. Recall that an object in a collection object, like an array, is owned by the collection object. When you remove an object from a mutable collection object, like an
NSMutableArray
, the removed object loses an owner.

 
[items removeObject:p];    // object pointed to by p loses an owner

Keep in mind that losing an owner doesn’t necessarily mean that the object gets destroyed; if there is still another pointer to the object somewhere, then that object will continue to exist. However, when an object loses its last owner, it means certain and appropriate death.

 

Because objects own other objects, which can own other objects, the destruction of a single object can set off a chain reaction of loss of ownership, object destruction, and freeing up of memory.

 

We have an example of this in
RandomPossessions
. Take another look at the object diagram of this application.

 

Figure 3.6  Objects and pointers in RandomPossessions

 

In
main.m
, after you finish printing out the array of
BNRItem
s, you set the
items
variable to
nil
. Setting
items
to
nil
causes the array to lose its only owner, so that array is destroyed.

 

But it doesn’t stop there. When the
NSMutableArray
is destroyed, all of its pointers to
BNRItem
s are destroyed. Once these variables are gone, no one owns any of the
BNRItem
s, so they are all destroyed. Destroying a
BNRItem
destroys its instance variables, which leaves the objects pointed to by those variables unowned. So they get destroyed, too.

 

Let’s add some code so that we can see this destruction as it happens.
NSObject
implements a
dealloc
method, which is sent to an object when it is about to be destroyed. We can override this method in
BNRItem
to print something to the console when a
BNRItem
is destroyed. In
RandomPossessions.xcodeproj
, open
BNRItem.m
and override
dealloc
.

 
- (void)dealloc
{
    NSLog(@"Destroyed: %@", self);
}
 

In
main.m
, add the following line of code.

 
NSLog(@"Setting items to nil...");
items = nil;

Build and run the application. After the
BNRItem
s print out, you will see the message announcing that
items
is being set to
nil
. Then, you will see the destruction of each
BNRItem
logged to the console.

 

At the end, there are no more objects taking up memory, and only the
main
function remains. All this automatic clean-up and memory recycling occurs simply by setting
items
to
nil
. That’s the power of ARC.

 
Strong and Weak References

So far, we’ve said that anytime a pointer variable stores the address of an object, that object has an owner and will stay alive. This is known as a
strong reference
. However, a variable can optionally
not
take ownership of an object it points to. A variable that does not take ownership of an object is known as a
weak reference
.

 

A weak reference is useful for an unusual situation called a
retain cycle
. A retain cycle occurs when two or more objects have strong references to each other. This is bad news. When two objects own each other, they will never be destroyed by ARC. Even if every other object in the application releases ownership of these objects, these objects (and any objects that they own) will continue to exist by virtue of those two strong references.

 

Thus, a retain cycle is a memory leak that ARC needs your help to fix. You fix it by making one of the references weak. Let’s introduce a retain cycle in
RandomPossessions
to see how this works. First, we’ll give
BNRItem
instances the ability to hold another
BNRItem
(so we can represent things like backpacks and purses). In addition, a
BNRItem
will know which
BNRItem
holds it. In
BNRItem.h
, add two instance variables and accessors

 
@interface BNRItem : NSObject
{
    NSString *itemName;
    NSString *serialNumber;
    int valueInDollars;
    NSDate *dateCreated;
    
    BNRItem *containedItem;
    BNRItem *container;
}
+ (id)randomItem;
- (id)initWithItemName:(NSString *)name
        valueInDollars:(int)value
          serialNumber:(NSString *)sNumber;
- (void)setContainedItem:(BNRItem *)i;
- (BNRItem *)containedItem;
- (void)setContainer:(BNRItem *)i;
- (BNRItem *)container;
 

Implement the accessors in
BNRItem.m
.

 
- (void)setContainedItem:(BNRItem *)i
{
    containedItem = i;
    // When given an item to contain, the contained
    // item will be given a pointer to its container
    [i setContainer:self];
}
- (BNRItem *)containedItem
{
    return containedItem;
}
- (void)setContainer:(BNRItem *)i
{
    container = i;
}
- (BNRItem *)container
{
    return container;
}
 

In
main.m
, remove the code that populated the array with random items. Then create two new items, add them to the array, and make them point at each other.

 
#import
#import "BNRItem.h"
int main (int argc, const char * argv[])
{
    @autoreleasepool {
        NSMutableArray *items = [[NSMutableArray alloc] init];
        
for (int i = 0; i < 10; i++) {
            
BNRItem *p = [BNRItem randomItem];
            
[items addObject:p];
        
}
        
for (BNRItem *item in items)
            
NSLog(@"%@", item);
    
        BNRItem *backpack = [[BNRItem alloc] init];
        [backpack setItemName:@"Backpack"];
        [items addObject:backpack];
        BNRItem *calculator = [[BNRItem alloc] init];
        [calculator setItemName:@"Calculator"];
        [items addObject:calculator];
        [backpack setContainedItem:calculator];
        NSLog(@"Setting items to nil...");
        items = nil;
    }
    return 0;
}

Here’s what the application looks like now:

 

Figure 3.7  RandomPossessions with retain cycle

 

Per our understanding of memory management so far, both
BNRItem
s should be destroyed along with their instance variables when
items
is set to
nil
. Build and run the application. Notice that the console does not report that these objects have been destroyed.

 

This is a retain cycle: the backpack and the calculator have strong references to one another, so there is no way to destroy these objects.
Figure 3.8
shows the objects in the application that are still taking up memory once
items
has been set to
nil
.

 

Figure 3.8  A retain cycle!

 

The two
BNRItem
s cannot be accessed by any other part of the application (in this case, the
main
function), yet they still exist in their own little world doing nothing useful. Moreover, because they cannot be destroyed, neither can the other objects that their instance variables point to.

 

To fix this problem, one of the pointers between the
BNRItem
s needs to be a weak reference. To decide which one should be weak, think of the objects in the cycle as being in a parent-child relationship. In this relationship, the parent can own its child, but a child should never own its parent. In our retain cycle, the backpack is the parent, and the calculator is the child. Thus, the backpack can keep its strong reference to the calculator (
containedItem
), but the calculator’s reference to the backpack (
container
) should be weak.

 

To declare a variable as a weak reference, we use the
__weak
attribute. In
BNRItem.h
, change the
container
instance variable to be a weak reference.

 
__weak
 BNRItem *container;

Build and run the application again. This time, the objects are destroyed properly.

 

Every retain cycle can be broken down into a parent-child relationship. A parent typically keeps a strong reference to its child, so if a child needs a pointer to its parent, that pointer must be a weak reference to avoid a retain cycle.

 

A child holding a strong reference to its
parent’s
parent also causes a retain cycle. So the same rule applies in this situation: if a child needs a pointer to its parent’s parent (or its parent’s parent’s parent, etc.), then that pointer must be a weak reference.

 

It’s good to understand and look out for retain cycles, but keep in mind that they are quite rare. Also,
Xcode
has a
Leaks
tool to help you find them. We’ll see how to use this tool in
Chapter 21
.

 

An interesting property of weak references is that they know when the object they reference is destroyed. Thus, if the backpack is destroyed, the calculator automatically sets its
container
instance variable to
nil
. In
main.m
, make the following changes to see this happen.

 
NSMutableArray *items = [[NSMutableArray alloc] init];
BNRItem *backpack = [[BNRItem alloc] init];
[backpack setItemName:@"Backpack"];
[items addObject:backpack];
BNRItem *calculator = [[BNRItem alloc] init];
[calculator setItemName:@"Calculator"];
[items addObject:calculator];
[backpack setContainedItem:calculator];
NSLog(@"Setting items to nil...");
items = nil;
backpack = nil;
NSLog(@"Container: %@", [calculator container]);
calculator = nil;

Build and run the application. Notice that after the backpack is destroyed, the calculator reports that it has no container without any additional work on our part.

 

A variable can also be declared using the
__unsafe_unretained
attribute. Like a weak reference, an unsafe unretained reference does not take ownership of the object it points to. Unlike a weak reference, an unsafe unretained reference is not automatically set to
nil
when the object it points to is destroyed. This makes unsafe unretained variables, well, unsafe. To see an example, change
container
to be unsafe unretained in
BNRItem.h
.

 
__unsafe_unretained
 BNRItem *container;

Build and run the application. It will most likely crash. The reason? When the calculator was asked for its container within the
NSLog
function call, it obligingly returned its value – the address in memory where the non-existent backpack used to live. Sending a message to a non-existent object resulted in a crash. Oops.

 

As a novice iOS programmer, you won’t use
__unsafe_unretained
. As an experienced programmer, you probably won’t use it, either. It exists primarily for backwards compatibility: applications prior to iOS 5 could not use weak references, so to have similar behavior, they must use
__unsafe_unretained
.

 

Be safe. Change this variable back to
__weak
.

 
__weak
 BNRItem *container;
 

Here’s the current diagram of
RandomPossessions
. Notice that the arrow representing the
container
pointer variable is now a dotted line. A dotted line denotes a weak (or unsafe unretained reference). Strong references are always solid lines.

 

Figure 3.9  RandomPossessions with retain cycle avoided

 

Other books

The Death and Life of Gabriel Phillips by Stephen Baldwin, Mark Tabb
Iron Angel by Kay Perry
The Walking People by Mary Beth Keane
Fatal Lies by Frank Tallis
Coyote Blue by Christopher Moore
Damascus Gate by Robert Stone