天天看點

How Autorelease work?

Autorelease 

When you call a method and receive as a result what Chapter 5 calls a ready-made instance, how does memory management work? Consider, for example, this code:

NSArray* myArray = [NSArray array];

According to the golden rule of memory management, the object now pointed to by myArray doesn’t need memory management. You didn’t say alloc in order to get it, so you haven’t claimed ownership of it and you don’t need to release it (and shouldn’t do so). But how is this possible? How is the NSArray class able to vend an array that you don’t have to release without also leaking that object?

If you don’t see why this is mysterious, let’s use explicit memory management — that is, we’ll use pre-ARC code, so that all the retains and releases become visible — and please pretend that you are the NSArray class. How would you implement the array method so as to generate an array that the caller doesn’t have to memory-manage? Don’t say that you’d just call some other NSArray method that vends a ready-made instance; that merely pushes the same problem back one level. You are NSArray. Sooner or later, you will have to generate the instance from scratch, and return it:

- (NSArray*) array {

    NSArray* arr = [[NSArray alloc] init];

    return arr; // hmmm, not so fast...

}

This, it appears, can’t work. We generated arr’s value by saying alloc. By the golden rule of memory management, this means we must also release the object pointed to by arr. But when can we possibly do this? If we do it before returning arr, arr will be pointing to garbage and we will be vending garbage. But we cannot do it after returning arr, because our method exits when we say return!

Apparently, we need a way to vend this object without decrementing its retain count now (so that it stays in existence long enough for the caller to receive and work with it), yet ensure that we will decrement its retain count (to balance our alloc call and fulfill our own management of this object’s memory). The solution, which is explicit in pre-ARC code, is autorelease:

- (NSArray*) array {

    NSArray* arr = [[NSArray alloc] init];

    [arr autorelease];

    return arr;

}

Or, because autorelease returns the object to which it is sent, we can condense that:

- (NSArray*) array {

    NSArray* arr = [[NSArray alloc] init];

    return [arr autorelease];

}

Here’s how autorelease works. Your code runs in the presence of something called an autorelease pool. (If you look in main.m, you can actually see an autorelease pool being created.) When you send autorelease to an object, that object is placed in the autorelease pool, and a number is incremented saying how many times this object has been placed in this autorelease pool. From time to time, when nothing else is going on, the autorelease pool is automatically drained. This means that the autorelease pool sends release to each of its objects, the same number of times as that object was placed in this autorelease pool, and empties itself of all objects. If that causes an object’s retain count to be zero, so be it; the object is destroyed in the usual way. So autorelease is just like release — effectively, it is a form of release — but with a proviso, “later, not right this second.”

You don’t need to know exactly when the current autorelease pool will be drained; indeed, you can’t know (unless you force it, as we shall see). The important thing is that in a case like our method array, there will be plenty of time for whoever called array to continue working with the vended object, and to retain the vended object if desired.

The vended object in a case like our method array is called an autoreleased object. The object that is doing the vending has in fact completed its memory management of the vended object. The vended object thus potentially has a zero retain count. But it doesn’t have a zero retain count just yet. The vended object is not going to vanish right this second, just after your call to [NSArray array], because your code is still running and so the autorelease pool is not going to be drained right this second. The recipient of such an object needs to bear in mind that this object may be autoreleased. The object won’t vanish while the code that called the method that vended the object is running, but if the receiving object wants to be sure that the vended object will persist later on, it should retain it.

That’s why you have no memory management responsibilities after receiving a ready-made instance, as with our call to [NSArray array]. An instance you receive by means other than those listed in the golden rule of memory management isn’t under your ownership. Either some other object owns it, or it is autoreleased. If some other object owns it, then this instance won’t be destroyed unless the object that owns it is stops owning it (as with the NSMutableArray example in the previous section) or is destroyed; if you’re worried that that might happen, you should take ownership of the object by retaining it (and then it will be up to you to release it later). If the instance is autoreleased, then it will certainly persist long enough for your code to finish (because the autorelease pool won’t drain while your code is still running); again, if you need it to persist longer than that, you should take ownership by retaining it.

Under ARC, as you might expect, all the right things happen of their own accord. You don’t have to say autorelease, and indeed you cannot. Instead, ARC will say it for you. And it says it in accordance with the method naming rule I described earlier. A method called array, for example, does not start with a camelCase unit new, init, alloc, copy, or mutableCopy. Therefore it must return an object whose memory management is balanced, using autorelease for the last release. ARC will see to it that this is indeed the case. On the other side of the ledger, the method that called array and received an object in return must assume that this object is autoreleased and could go out of existence if we don’t retain it. That’s exactly what ARC does assume.

Sometimes you may wish to drain the autorelease pool immediately. Consider the following:

for (NSString* aWord in myArray) {

    NSString* lowerAndShorter = [[aWord lowercaseString] substringFromIndex:1];

    [myMutableArray addObject: lowerAndShorter];

}

Every time through that loop, two objects are added to the autorelease pool: the lowercase version of the string we start with, and the shortened version of that. The first object, the lowercase version of the string, is purely an intermediate object: as the current iteration of the loop ends, no one except the autorelease pool has a pointer to it. If this loop had very many repetitions, or if these intermediate objects were themselves very large in size, this could add up to a lot of memory. These intermediate objects will all be released when the autorelease pool drains, so they are not leaking; nevertheless, they are accumulating in memory, and in certain cases there could be a danger that we will run out of memory before the autorelease pool drains. The problem can be even more acute than you think, because you might repeatedly call a built-in Cocoa method that itself, unbeknownst to you, generates a lot of intermediate autoreleased objects.

The solution is to intervene in the autorelease pool mechanism by supplying your own autorelease pool. This works because the autorelease pool used to store an autoreleased object is the most recently created pool. So you can just create an autorelease pool at the top of the loop and drain it at the bottom of the loop, each time through the loop. In modern Objective-C, the notation for doing this is to surround the code that is to run under its own autorelease pool with the directive @autoreleasepool{}, like this:

for (NSString* aWord in myArray) {

    @autoreleasepool {

        NSString* lowerAndShorter =

            [[aWord lowercaseString] substringFromIndex:1];

        [myMutableArray addObject: lowerAndShorter];

    }

}

Many classes provide the programmer with two equivalent ways to obtain an object: either an autoreleased object (ready-made instance) or an object that you create yourself with alloc and some form of init (instantiation from scratch). So, for example, NSMutableArray supplies both the class method array and the instance method init. Which should you use? In general, where you can generate an object with alloc and some form of init, it is probably better to do so. This policy prevents your objects from hanging around in the autorelease pool and keeps your use of memory as low as possible.