五:Autorelease Pools

这一节相对于上几节,详细讲解了Autorelease Pools

1.Autorelease Pools综述

Autorelease Pools是NSAutorelease的实例,它是一个容器,容纳各种接到autorelease消息的objects。一个object可以被多次放入autorelease pools中,放进去几次就会release几次,不会造成内存泄露。而且,相比较release而言,autorelease延长了object的生命周期。

Cocoa总是相信你有可用的autorelease pool,这也是我们在iOS开发中的main文件中看到autorelease pool的缘由。当然你可以自己alloc一个autorelease pool,并且用drain释放。值得注意的是,不要对autorelease pool发送autorelease或retain或release,而且你drain掉autorelease pool时应该是在同一context(这个词解释不好,还是直接英文,大致就是比如说在同一个函数,同一个循环等)。

Autorelease pools是以栈的形式整理的,而而不是一般认为的内嵌方式。也就是说你新创建的autorelease pool放在栈顶,并且这事你对一个object实行autorelease是把它放在栈顶那个autorelease pool。

为什么autorelease pool被误认为内嵌方式也是情有可原,因为它在外表现形式确实是内嵌,比如你在main文件看到最外层的autorelease pool,当你进入appdelegate时,就可以创建算是内嵌的auotrelease pool。

有三种情况你值得创建自己的autorelease pool:

  • 基于Application Kit的程序是自动创建一个autorelease pool的,但如果你写的是像命令行程序那样非基于Application Kit的程序,就要自己创建了。
  • 如果你写多线程程序,那么在新开的线程里需要自己写autorelease pool,而且是一开线程就创建,外面那个它不能使用。
  • 如果你写了一个循环,这个循环包含大量暂时的object,那么为了内存不告急,你应该在这个循环内创建一个autorelease pool,并把那些暂时的object都扔进去。
2.在不基于AppKit的程序里创建Autorelease Pools

这个还是代码更加直观:

void main()
{
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];

    NSArray *args = [[NSProcessInfo processInfo] arguments];

    for (NSString *fileName in args) {

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

        NSError *error = nil;
        NSString *fileContents = [[[NSString alloc] initWithContentsOfFile:fileName
                                           encoding:NSUTF8StringEncoding error:&error] autorelease];

        /* Process the string, creating and autoreleasing more objects. */

        [loopPool drain];
    }

    /* Do whatever cleanup is needed. */
    [pool drain];

    exit (EXIT_SUCCESS);
}

在for循环也创建了一个,减少内存消耗,其他应该没啥可说。

3.Autorelease Pools和多线程

为什么多开的线程不能使用原来就创建的autorelease pools呢?主要是因为,每一个线程都独立得维护着自己特有的autorelease pool堆栈,不同线程间的autorelease pools不能交错使用。如果你新开的线程涉及到大量objects的创建,你值得开一个autorelease pool,而且可以完全仿照AppKit自带的,也不麻烦。

4.始终遵循所有权基本规则

通过autorelease而不是release,使object延长了生命周期。要注意的是,如果一个object已经autorelease了,所有权已经放弃了,就没必要再release了。而且你也不能再去访问它。但有时我们确实需要把autorelease pool范围内的object拉出来在这个autorelease pool范围外使用,你可以retain一下这个object,然后在autorelease pool被drain掉以后,并且使用完这个object后把它autorelease掉,代码如下:

– (id)findMatchingObject:(id)anObject
{
    id match = nil;

    while (match == nil) {
        NSAutoreleasePool *subPool = [[NSAutoreleasePool alloc] init];

        /* Do a search that creates a lot of temporary objects. */
        match = [self expensiveSearchForObject:anObject];

        if (match != nil) {
            [match retain]; /* Keep match around. */
        }
        [subPool drain];
    }

    return [match autorelease];   /* Let match go and return it. */
}

仔细观察可发现这个match不是在subPool这个autorelease pool,而是在上一级的autorelease pool里了