autorelease && autorelease pool block

autorelease pool block提供了一个允许我们延时deallocated对象的机制

autorelease

NSAutoReleasePool对象的生存周期相当于C语言变量的作用域。对于所有调用过autorelease的实例方法,会在废弃NSAutorelease对象时,调用release方法

NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
id obj = [[NSObject alloc] init];
[obj autorelease];
[pool drain]; //调用pool drain方法相当于调用[obj release]

在Cocoa框架也有很多可以返回autorelease对象的方法

//返回autorelease对象
id array = [NSMutableArray Array];
//相当与
id array = [[[NSMutableArray alloc] init] autorelease];

autorelease 原理

在NSAutoreleasePool维持了一个数组来保存autorelase的对象,[obj autorelease]方法实际上是调用了NSAutoreleasePool[NSAutoreleasePool addObject:obj]方法,该方法会找到当前的pool并add到数组中,当调用[pool drain]时会废弃当前pool对象,并释放内部数组,因此内部所有对象都会被调用release方法

可以使用[NSAutoreleasePool showPools];这个非公开方法查看现在的pool中对象状况,查看结果类似下面输出,可以用其查看对象是否被自动release

objc[41725]: ##############
objc[41725]: AUTORELEASE POOLS for thread 0x10cd6f5c0
objc[41725]: 2337 releases pending.
objc[41725]: [0x7ff2b4000000]  ................  PAGE (full)  (cold)
objc[41725]: [0x7ff2b4000038]    0x6000012fd180  __NSArrayI
objc[41725]: [0x7ff2b4000040]    0x6000024aa1c0  __NSSetI
objc[41725]: [0x7ff2b4000048]  ################  POOL 0x7ff2b4000048
objc[41725]: [0x7ff2b4000050]    0x6000007a8480  __NSCFString
...
objc[41725]: [0x7ff2b4000058]  ################  POOL 0x7ff2b4000058
objc[41725]: [0x7ff2b406ea10]  ################  POOL 0x7ff2b406ea10
objc[41725]: [0x7ff2b406ea18]    0x6000005a77c0  NSObject
objc[41725]: ##############

autorelease pool block

在Cocoa框架中,主循环的RunLoop会对NSAutoreleasePool进行生成、持有和废弃,因此,通常,我们不需要创建自己的自动释放池,但是有些情况下我怕们需要这么做
在autorelease pool block结束时,所有在block内部收到autorelease消息的对象都会被发送一个release消息

autorelease pool block可以嵌套:

@autoreleasepool {
    // . . .
    @autoreleasepool {
        // . . .
    }
    . . .
}

Cocoa中代码都是在自动释放池block中执行,否则会自动释放的对象未被释放造成内存泄漏。(因此,如果在自动释放池block外执行走动式房消息,Cocoa会报错)。AppKit或者UIKit框架都在自动释放池block中处理每个事件循环(详情见runLoop)。因此,通常不需要创建autorelease pool block,但是以下三种情况可能需要使用自己的自动释放池

  • 假如编写不基于UI框架的程序,例如命令行工具
  • 假如编写一个创建了很多临时对象的循环
    可以再循环内的下一次迭代之前处理这些对象,有助于减少应用程序的最大内存占用量
  • 如果你创建了一个辅助线程
    当你的线程开始执行时,必须创建自己的自动释放池,否则你的应用程序有可能会内存泄漏

使用本地自动释放池block减少峰值内存占用

NSArray *urls = <# An array of file URLs #>;
for (NSURL *url in urls) {
 
    @autoreleasepool {
        NSError *error;
        NSString *fileContents = [NSString stringWithContentsOfURL:url
                                         encoding:NSUTF8StringEncoding error:&error];
        /* Process the string, creating and autoreleasing more objects. */
    }
}

在上面例子中,for循环每次循环的自动释放block结束时,将自动释放对象(例如:fileContents)发送release消息

在自动释放池block后,块中的自动释放对象不应该再向其发送消息或者返回。如果想要将其返回,则可以先将其retain然后在block外将其autorelease

– (id)findMatchingObject:(id)anObject {
 
    id match;
    while (match == nil) {
        @autoreleasepool {
 
            /* Do a search that creates a lot of temporary objects. */
            match = [self expensiveSearchForObject:anObject];
 
            if (match != nil) {
                [match retain]; /* Keep match around. */
            }
        }
    }
 
    return [match autorelease];   /* Let match go and return it. */
}

线程和自动释放池block

Cocoa应用程序的每个线程都会维护自己的自动释放池堆栈。如果编写基于Foundation的程序或者分离线程,则你必须创建自己的autoreleasePool

如果应用程序或者线程长时间运行,则有可能产生大量自动释放对象,此时就需要使用自动释放池(例如主线程的UIKit和AppKit);否则自动释放对象会累计,造成内存增加。但,如果你的线程没有进行Cocoa调用,则不需要使用自动释放池block

注意:
如果使用POSIX线程API而不是NSThread创建辅助线程,则除非Cocoa处于多线程模式,否则不能使用CocoaCocoa仅在分离其第一个NSThread对象后才进入多线程模式。 要在辅助POSIX线程上使用Cocoa,您的应用程序必须首先分离至少一个NSThread对象,该对象可以立即退出。 您可以使用NSThread类方法isMultiThreaded测试Cocoa是否处于多线程模式。