Home > Articles > Programming > Mac and iOS Programming

  • Print
  • + Share This
  • 💬 Discuss
This chapter is from the book

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 <Foundation/Foundation.h>
#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

Figure 3.7 RandomPossessions with retain cycle

Per our understanding of memory management so far, both BNRItems 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

Figure 3.8 A retain cycle!

The two BNRItems 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 BNRItems 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

Figure 3.9 RandomPossessions with retain cycle avoided

  • + Share This
  • 🔖 Save To Your Account

Discussions

comments powered by Disqus