Implementing Autorelease

来源:互联网 发布:p2p网络借贷新闻 编辑:程序博客网 时间:2024/06/14 01:26

Autorelease

Because of its name, you might think that autorelease is something like ARC. But it is not. It is more like “automatic variable” in the C language.7

Let’s start by reviewing what automatic variable is in C. We then look at the source code of GNUstep to understand how autorelease works, followed by Apple’s implementation of autorelease.

Automatic Variables

An automatic variable is a lexically scoped variable, which is disposed of automatically when the execution leaves the scope.

{
    int a;
}

/*
   * Because the variable scope is left,
   * auto variable 'int a' is disposed of and can't be accessed anymore.
   */

With autorelease, you can use objects in the same manner as automatic variables, meaning that when execution leaves a code block, the “release” method is called on the object automatically. You can control the block itself as well.

__________

7 Wikipedia, “Automatic Variable,” http://en.wikipedia.org/wiki/Automatic_variable

The following steps and Figure 1–12 show you how to use the “autorelease” instance method.

  1. Create an NSAutoreleasePool object.
  2. Call “autorelease” to allocated objects.
  3. Discard the NSAutoreleasePool object.
images

Figure 1–12. Lifetime of an NSAutoreleasePool object

A code block between the creation and disposal of the NSAutoreleasePool object is equivalent to the variable scope in C. When an NSAutoreleasePool object is disposed of, the release method is automatically called for all the autoreleased objects. Some example source code is as follows.

NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
id obj = [[NSObject alloc] init];
[obj autorelease];
[pool drain];

In the last line of the above source code, [pool drain] will do [obj release].

In the Cocoa Framework, NSAutoreleasePool objects are created, owned, or disposed of all over the place, such as NSRunLoop, which is the main loop of the application (Figure 1–13). So, you don’t need to use the NSAutoreleasePool object explicitly.

images

Figure 1–13. A NSAutoreleasePool object is created and disposed of each time in NSRunLoop.

But when there are too many autoreleased objects, application memory becomes short (Figure 1–14). It happens because the objects still exist until the NSAutoreleasePool object is discarded. A typical example of this is loading and resizing many images. Many autoreleased objects, such as NSData objects for reading files, UImage objects for the data, and resized images exist at the same time.

for (int i = 0; i < numberOfImages; ++i) {
    /*
      * Processing images, such as loading,etc.
      * Too many autoreleased objects exist,
      * because NSAutoreleasePool object is not discarded.
      * At some point, it causes memory shortage.
      */
}
images

Figure 1–14. The increasing number of autoreleased objects

In this case, you should create and discard an NSAutoreleasePool object by yourself explicitly at the appropriate time (Figure 1–15).

for (int i = 0; i < numberOfImages; ++i) {
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];

    /*
      * Loading images, etc.
      * Too many autoreleased objects exist.
      */

    [pool drain];

    /*
      * All the autoreleased objects are released by [pool drain].
      */
}
images

Figure 1–15. Autoreleased object should be properly released.

With the Cocoa Framework, you will see many class methods returning autoreleased objects, such as the “arrayWithCapacity” method of the NSMutableArray class.

id array = [NSMutableArray arrayWithCapacity:1];

The above source code is equivalent to:

id array = [[[NSMutableArray alloc] initWithCapacity:1] autorelease];

Implementing autorelease

In this section, we discuss the implementation of autorelease in GNUstep as we did forallocretainrelease, and dealloc to learn how it works in detail.

[obj autorelease];

This source code calls the NSObject instance method “autorelease”. Listing 1–9 shows the implementation of the autorelease method.

Listing 1–9. GNUstep/Modules/Core/Base/Source/NSObject.m autorelease

- (id) autorelease
{
    [NSAutoreleasePool addObject:self];
}

Actually, autorelease just calls the NSAutoreleasePool class method addObject. In GNUstep, it is implemented bit different. But this is just for optimization as you can see below.

OPTIMIZATION ON OBJECTIVE-C METHOD CALL

In GNUstep, the autorelease method is implemented in an irregular way for optimization purposes. Because autorelease is called so often in iOS and OSX applications, it has a special mechanism called IMP caching. When the framework is initialized, it caches some search results, such as function pointers and name resolution of classes and methods. If the mechanism doesn’t exist, these procedures have to be done when autorelease is called.

id autorelease_class = [NSAutoreleasePool class];
SEL autorelease_sel = @selector(addObject:);
IMP autorelease_imp = [autorelease_class methodForSelector: autorelease_sel];

When the method is called, it just returns a cached value.

- (id) autorelease
{
    (*autorelease_imp)(autorelease_class, autorelease_sel, self);
}

Above is the method call with IMP caching. It can be rewritten as follows if IMP caching is not there. It is about two times faster with the caching mechanism though, depending on the environment.

- (id) autorelease
{
    [NSAutoreleasePool addObject:self];
}

Let’s see the implementation of the NSAutoreleasePool class. Listing 1–10 is a simplified source code in NSAutoreleasePool.

Listing 1–10. GNUstep/Modules/Core/Base/Source/NSAutoreleasePool.m addObject

+ (void) addObject: (id)anObj
{
    NSAutoreleasePool *pool = getting active NSAutoreleasePool;
    if (pool != nil) {
        [pool addObject:anObj];
    } else {
        NSLog(@"autorelease is called without active NSAutoreleasePool.");
    }
}

The Class method “addObject” calls NSAutoreleasePool instance method “addObject” for the active NSAutoreleasePool object. In the next example, a variable “pool” is the active NSAutoreleasePool object.

NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
id obj = [[NSObject alloc] init];
[obj autorelease];

When multiple NSAutoreleasePool objects are created and nested, the innermost object becomes active. In the next example, pool2 is active.

NSAutoreleasePool *pool0 = [[NSAutoreleasePool alloc] init];

    NSAutoreleasePool *pool1 = [[NSAutoreleasePool alloc] init];

        NSAutoreleasePool *pool2 = [[NSAutoreleasePool alloc] init];

        id obj = [[NSObject alloc] init];
        [obj autorelease];

        [pool2 drain];

    [pool1 drain];

[pool0 drain];

Next, let’s take a look at the implementation of the NSAutoreleasePool instance method addObject as well (Listing 1–11).

Listing 1–11. GNUstep/Modules/Core/Base/Source/NSAutoreleasePool.m addObject

- (void) addObject: (id)anObj
{
    [array addObject:anObj];
}

It adds the object to a mutable array. In the original GNUstep implementation, linked list is used instead of array. Anyway, the object is stored in a container, which means that when the instance method “autorelease” of NSObject is called, the object is added to the container in an active NSAutoreleasePool object.

[pool drain];

Next, let’s see how the active NSAutoreleasePool object is disposed of when drain is called (Listing 1–12).

Listing 1–12. GNUstep/Modules/Core/Base/Source/NSAutoreleasePool.m drain

- (void) drain
{
    [self dealloc];
}

- (void) dealloc
{
    [self emptyPool];
    [array release];
}

- (void) emptyPool
{
    for (id obj in array) {
        [obj release];
    }
}

We can see that the “release” method is called for all the objects in the pool.

Apple’s Implementation of autorelease

We can see Apple’s implementation of autorelease in runtime/objc-arr.mm in the objc4 library. The source code is shown in Listing 1–13.

Listing 1–13. objc4/runtime/objc-arr.mm class AutoreleasePoolPage

class AutoreleasePoolPage
{
    static inline void *push()
    {
        /* It corresponds to creation and ownership of an NSAutoreleasePool object */
    }

    static inline void pop(void *token)
    {
        /* It corresponds to disposal of an NSAutoreleasePool object */
        releaseAll();
    }

    static inline id autorelease(id obj)
    {
        /* It corresponds to NSAutoreleasePool class method addObject. */
        AutoreleasePoolPage *autoreleasePoolPage = /* getting active AutoreleasePoolPage
object */
        autoreleasePoolPage->add(obj);
    }

    id *add(id obj)
    {
        /* add the obj to an internal array; */
    }

    void releaseAll()
    {
        /* calls release for all the objects in the internal array */
    }
};

void *objc_autoreleasePoolPush(void)
{
    return AutoreleasePoolPage::push();
}

void objc_autoreleasePoolPop(void *ctxt)
{
    AutoreleasePoolPage::pop(ctxt);
}

id objc_autorelease(id obj)
{
    return AutoreleasePoolPage::autorelease(obj);
}

The functions and the AutoreleasePoolPage class are implemented using a C++ class and a dynamic array. The functions seem to work the same as in GNUstep. As we didpreviously with the debugger, we investigate what functions are called in the autorelease and NSAutoreleasePool class methods. These methods will call objc4 functions, which are related to autorelease:

NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
/* equivalent to objc_autoreleasePoolPush() */

id obj = [[NSObject alloc] init];

[obj autorelease];
/* equivalent to objc_autorelease(obj)  */

[pool drain];
/* equivalent to objc_autoreleasePoolPop(pool) */

By the way, in iOS, the NSAutoreleasePool class has a method to check the status of autoreleased objects. The method, showPools, displays the current status of NSAutoreleasePool to the console. It can be used only for debugging purposes because it is a private method. You can use it as

[NSAutoreleasePool showPools];

With the latest Objective-C runtime, instead of the “showPools” method,_objc_autoreleasePoolPrint() is provided because “showPools” works in iOS only, This method is also a private method so you can use it for debugging purposes only.

/* declare function */
extern void _objc_autoreleasePoolPrint();

/* display autoreleasepool status for debug. */
_objc_autoreleasePoolPrint();

Then you can see the status of AutoreleasePoolPage. The result is as follows.

objc[14481]: ##############
objc[14481]: AUTORELEASE POOLS for thread 0xad0892c0
objc[14481]: 14 releases pending.
objc[14481]: [0x6a85000]  ................  PAGE  (hot) (cold)
objc[14481]: [0x6a85028]  ################  POOL 0x6a85028
objc[14481]: [0x6a8502c]         0x6719e40  __NSCFString
objc[14481]: [0x6a85030]  ################  POOL 0x6a85030
objc[14481]: [0x6a85034]         0x7608100  __NSArrayI
objc[14481]: [0x6a85038]         0x7609a60  __NSCFData
objc[14481]: [0x6a8503c]  ################  POOL 0x6a8503c
objc[14481]: [0x6a85040]         0x8808df0  __NSCFDictionary
objc[14481]: [0x6a85044]         0x760ab50  NSConcreteValue
objc[14481]: [0x6a85048]         0x760afe0  NSConcreteValue
objc[14481]: [0x6a8504c]         0x760b280  NSConcreteValue
objc[14481]: [0x6a85050]         0x760b2f0  __NSCFNumber
objc[14481]: [0x6a851a8]  ################  POOL 0x6a851a8
objc[14481]: [0x6a851ac]         0x741d1e0  Test
objc[14481]: [0x6a851b0]         0x671c660  NSObject
objc[14481]: ##############

It is very useful to know if some objects are autoreleased or not, as noted in the following sidebar.

AUTORELEASE NSAUTORELEASEPOOL OBJECT

Question: What will happen if “autorelease” is called on an NSAutoreleasePool object?

NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
[pool autorelease];

Answer: The application will be terminated.

*** Terminating app due to uncaught exception 'NSInvalidArgumentException'
reason: '*** -[NSAutoreleasePool autorelease]:
Cannot autorelease an autorelease pool'

When autorelease is called in Objective-C with the Foundation framework, an NSObject instance method is called for in almost all the cases. However, the NSAutoreleasePool class overrides autorelease to show an error when autorelease is called on autoreleasepool.

0 0
原创粉丝点击