Effective Objective-C 2.0: Item 29: Understand Reference Counting

来源:互联网 发布:斗牛牛软件下载 编辑:程序博客网 时间:2024/06/06 00:41

Item 29: Understand Reference Counting

Objective-C uses reference counting for memory management, meaning that every object has a counter that is incremented and decremented. You increment the counter when you want to register your interest in keeping an object alive, and you decrement the counter when you have finished with it. When an object’s counter reaches zero, the object no longer has anything interested in it and is free to be destroyed. That is a brief overview; understanding the topic fully is crucial to writing good Objective-C code, even if you are going to be using ARC (see Item 30).

The garbage collector for use with Objective-C code written for Mac OS X is officially deprecated as of Mac OS X 10.8 and has never been available for iOS. Understanding reference counting is very important going forward because you can no longer rely on the garbage collector on Mac OS X and you never have, nor will you be able to, on iOS.

If you already use ARC, you should switch off the part of your brain that tells you that most of the code presented won’t compile. That’s true, in an ARC world. But this item is explaining reference counting from an Objective-C perspective, which is still going on with ARC, and to do so requires showing code that uses methods that are explicitly illegal under ARC.

How Reference Counting Works

Under reference-counting architectures, an object is assigned a counter to indicate how many things have an interest in keeping that object alive. This is referred to as the retain count in Objective-C but can also be referred to as the reference count. The following three methods declared on the NSObject protocol can manipulate that counter to either increment it or decrement it:

Image retain Increment the retain count.

Image release Decrement the retain count.

Image autorelease Decrement the retain count later, when the autorelease pool is drained. (The autorelease pool is discussed further on page 150 and in Item 34.)

A method to inspect the retain count, called retainCount, is generally not very useful, even when debugging, so I (and Apple) encourage you not to use it. See Item 36 for more information.

An object is always created with a retain count of at least 1. Interest in keeping the object alive is indicated by invoking the retain method. When that interest has gone because the object is no longer required by a certain portion of code, release or autorelease is called. When the retain count finally reaches 0, the object is deallocated, meaning that its memory is marked for reuse. Once this has happened, any references to that object are no longer valid.

Figure 5.1 illustrates an object going through creation, a retain, and then two releases.

Image

Figure 5.1 An object’s retain count incrementing and decrementing as it goes through its life cycle

Many objects will be created throughout an application’s life cycle. These objects all become related to one another. For example, an object representing a person has a reference to string objects for the person’s names and might also have references to other person objects, such as in a set representing the friends of the person, thereby forming what is known as an object graph. Objects are said to own other objects if they hold a strong reference to them. This means that they have registered their interest in keeping them alive by retaining them. When they are finished with them, they release them.

In the Figure 5.2 object graph, ObjectA is being referenced by both ObjectB and ObjectC. When both ObjectB and ObjectC have finished with ObjectA, its retain count drops to 0 and it can be destroyed. Both ObjectB and ObjectC are being kept alive by other objects, which inturn are being kept alive by other objects. Eventually, if you went up the tree of what is referencing what, you would come to a root object. In the case of Mac OS X applications, this could be the NSApplication object; in the case of iOS applications, the UIApplication object.Both are singletons created when an application launches.

Image

Figure 5.2 An object graph showing an object eventually being deallocated after all references to it are released

The following code will help you to understand this in practice:

NSMutableArray *array = [[NSMutableArray allocinit];

NSNumber *number = [[NSNumber allocinitWithInt:1337];
[array addObject:number];
[number release];

// do something with 'array'

[array release];

As explained previously, this code won’t compile under ARC, owing to the explicit calls toreleaseIn Objective-C, a call to alloc will result in the return of an object that is said to be owned by the caller. That is to say, the caller’s interest has already been registered because it used alloc. However, it is important to note that this does not necessarily mean that the retain count is exactly 1. It might be more, since the implementation of either alloc or initWithInt:may mean that other retains have been made on the object. What is guaranteed, though, is that the retain count is at least 1. You should always think about retain counts in this way. You should never guarantee what a retain count is, only what effect your actions have had on the retain count: whether that is incremented or decremented.

The number object is then added to the array. In doing this, the array also registers its interest by calling retain on the number object within the addObject: method. At this point, the retain count is at least 2. Then, the number object is no longer required by this code, so it is released. Its retain count is now back down to at least 1. At this point, the number variable can no longer be safely used. The call to release means that the object pointed to is no longer guaranteed to be alive. Of course, the code in this scenario makes it obvious that it will be alive after the call toreleasesince the array is also still referencing it. However, that should never be assumed, which means that you should not do something like this:

NSNumber *number = [[NSNumber allocinitWithInt:1337];
[array addObject:number];
[number release];
NSLog(@"number = %@", number);

Even though the code will work in this scenario, it is not good practice. If for any reason thenumber object were deallocated while calling release as its retain count dropped to zero, the call to NSLog would potentially crash. The reason I qualify that with “potentially” is that when an object is deallocated, its memory is simply returned to the available pool. If the memory has not been overwritten by the time the NSLog runs, the object will still exist, and there won’t be a crash. For this reason, bugs where objects have been released too early are often difficult to debug.

To mitigate accidentally using an object that is no longer valid, you will often see a releasefollowed by nilling out the pointer. This ensures that nothing can access a pointer to a potentially invalid object, often referred to as a dangling pointer. For example, it can be done like this:

NSNumber *number = [[NSNumber allocinitWithInt:1337];
[array addObject:number];
[number release];
number = nil;

Memory Management in Property Accessors

As described earlier, objects form an object graph by being linked together. The array in the example holds onto the objects it contains by retaining them. In the same way, other objects will hold onto other objects, often through the use of properties (see Item 6), which use accessors to get and set instance variables. If the property is a strong relationship, the value of the property is retained. A setter accessor for such a property called foo, backed by an instance variable called _foo, would look like this:

- (void)setFoo:(id)foo {
    [foo retain];
    [_foo release];
    _foo = foo;
}

The new value is retained, and the old value is released. Then the instance variable is updated to point to the new value. The order is important. If the old value was released before the new value was retained and the two values are exactly the same, the release would mean that the object could potentially be deallocated prematurely. The subsequent retain could not resurrect the deallocated object, and the instance variable would be a dangling pointer.

Autorelease Pools

A feature that is important to Objective-C’s reference-counting architecture is what is known as autorelease pools. Instead of calling release to immediately decrement an object’s retain count (and potentially deallocate it), you can also call autorelease, which performs the release sometime later, usually the next time around the event loop, but it can also happen sooner (seeItem 34).

This feature is very useful, especially when returning an object from a method. In this case, you don’t always want to return it as owned by the caller. For example, consider the following method:

- (NSString*)stringValue {
    NSString *str = [[NSString alloc]
                         initWithFormat:@"I am this: %@"self];
    return str;
}

In this case, str is returned with a +1 retain count, since the call to alloc returns with a +1 count, and there is no balancing release. The meaning of +1 here is that you as the caller are responsible for one retain. You must somehow balance that one retain. This does not mean that the retain count is exactly 1, however. It might be more, but that is implementation detail within the method initWithFormat:. All you need to worry about is balancing that one retain.

However, you cannot release str inside the method, because it would then immediately be deallocated before being returned. So an autorelease is used to indicate that the object should be released sometime later but guaranteed to be long enough in the future that the returned value can be retained by the caller if it needs to hold onto it. In other words, the object is guaranteed to be alive across the method call boundary. In fact, the release will happen when the outermost autorelease pool is drained (see Item 34), which, unless you have your own autorelease pools, will be next time around the current thread’s event loop. Applying this to thestringValue method gives the following:

- (NSString*)stringValue {
    NSString *str = [[NSString alloc]
                     initWithFormat:@"I am this: %@"self];
    return [str autorelease];
}

Now the returned NSString object will definitely be alive when it is returned to the caller. So the object can be used like this:

NSString *str = [self stringValue];
NSLog(@"The string is: %@", str);

No extra memory management is required here, since the str object is returned autoreleased and therefore is balanced. Since the release from being in the autorelease pool won’t happen until next time around the event loop, the object does not need to be explicitly retained to be used in the log statement. However, if the object needs to be held onto, such as being set to an instance variable, the object needs to be retained and subsequently released:

_instanceVariable = [[self stringValueretain];
// ...
[_instanceVariable release];

So autorelease is a way of extending the lifetime of an object just enough so that it can survive across method call boundaries.

Retain Cycles

A common scenario to be aware of with reference counting is what is known as a retain cycle, which occurs when multiple objects reference one another cyclically. This leads to memory leaks because no object in the cycle will ever see its retain count drop to 0. Each object has at least one other object in the cycle maintaining a reference to it. In Figure 5.3, three objects all have a single reference to one of the other two objects. In this cycle, all retain counts are 1.

Image

Figure 5.3 A retain cycle in an object graph

In a garbage-collected environment, this situation would usually be picked up as a so-called island of isolation. In such a scenario, the collector would deallocate all three objects. This luxury is not available under the reference-counting architecture of Objective-C. The problem is usually solved by using weak references (see Item 33) or an external influence causing one of the objects to relinquish its retain of another object. In either case, the retain cycle is broken, and memory is no longer leaked.

Things to Remember

Image Reference-counting memory management is based on a counter that is incremented and decremented. An object is created with a count of at least 1. An object with a positive retain count is alive. When the retain count drops to 0, the object is destroyed.

Image As it goes through its life cycle, an object is retained and released by other objects holding references to it. Retaining and releasing increments and decrements the retain count, respectively.

原创粉丝点击