`
linuxstuding
  • 浏览: 1227292 次
文章分类
社区版块
存档分类
最新评论

Objective-C内存管理编程指南(4)自动释放池

 
阅读更多

自动释放池

本章向您介绍如何微调您的应用程序对自动释放池的控制;有关使用自动释放机制的一般介绍请参考文档对象的所有权和销毁

自动释放池概述

自动释放池是一个NSAutoreleasePool实例,其中包含已经收到autorelease消息的其他对象;当自动释放池被回收时,它会向其中的每个对象发送一条release消息。一个对象可以被数次放入一个自动释放池中,并且在每次被放入池中的时候都会收到一条release消息。因此,向对象发送autorelease消息(而不是release消息)可以至少将该对象的生命周期延长至自动释放池本身被释放的时候(如果在此期间对象被保留,则它可以存活更久)。

Cocoa总是期望有一个自动释放池可用。如果自动释放池不可用,那么自动释放对象就无法得到释放,您也就泄漏了内存。如果当自动释放池不可用的时候,您发送了autorelease消息,那么Cocoa会记录相应的错误信息。

您可以使用常见的allocinit消息来创建一个NSAutoreleasePool对象,并使用drain(如果您向一个自动释放池发送autoreleaseretain消息,会引发异常要了解releasedrain之间的差异,请参考垃圾回收)销毁它。自动释放池应该总是在与它被创建时所处的相同上下文环境(方法或函数的调用,或循环体)中被销毁。

自动释放池被置于一个堆栈中,虽然它们通常被称为被嵌套的。当您创建一个新的自动释放池时,它被添加到堆栈的顶部。当自动释放池被回收时,它们从堆栈中被删除。当一个对象收到送autorelease消息时,它被添加到当前线程的目前处于栈顶的自动释放池中。

嵌套自动释放池的能力是指,您可以将它们包含进任何函数或方法中。例如,main函数可以创建一个自动释放池,并调用另一个创建了另外一个自动释放池的函数。或者,一个方法可以有一个自动释放池用于外循环,而有另一个自动释放池用于内循环。嵌套自动释放池的能力是一种很显著的优势,但是,当发生异常时也会有副作用(见自动释放池的作用域和嵌套自动释放池的含义)。

Application Kit会在一个事件周期(或事件循环迭代)的开端比如鼠标按下事件自动创建一个自动释放池,并且在事件周期的结尾释放它,因此您的代码通常不必关心。但是,有三种情况您应该使用您自己的自动释放池:

§ 如果您正在编写一个不是基于Application Kit的程序,比如命令行工具,则没有对自动释放池的内置支持;您必须自己创建它们。

§ 如果您生成了一个从属线程,则一旦该线程开始执行,您必须立即创建您自己的自动释放池;否则,您将会泄漏对象。(详情请参考自动释放池和线程。)

§ 如果您编写了一个循环,其中创建了许多临时对象,您可以在循环内部创建一个自动释放池,以便在下次迭代之前销毁这些对象。这可以帮助减少应用程序的最大内存占用量。

自动释放池是按序使用的。一般情况下,您不应该将自动释放池作为某个对象的实例变量。

Application Kit 程序中的自动释放池

在不是基于Application Kit的程序中,启用自动释放机制是很容易的。您可以简单地在main()函数的开始创建一个自动释放池,并在main()函数的最后释放它这是Xcode中的Foundation Tool模板使用的模式。这样就为任务的生命周期建立了一个自动释放池。但是,这也意味着在任务的生命周期中创建的所有的自动释放对象,都要等到任务完成时才会被销毁。这可能会导致任务的内存占用量不必要地增加。您也可以考虑创建具有更窄作用域的自动释放池。

许多程序具有深层循环,并在其中完成程序的大部分工作。为了减少内存占用峰值,您可以在该循环中一次迭代的开始创建一个自动释放池,并在本次迭代的最后销毁它。

您的main函数可能看起来类似清单1中的代码。

清单 1AppKit程序的main函数的例子

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循环每次处理一个文件。NSAutoreleasePool对象在循环开始的时候被创建,并在循环结束时被释放。因此,在循环内部,任何收到autorelease消息的对象(比如fileContents)都会被添加到loopPool中,并且,当loopPool在循环的最后被释放的时候,那些对象也会被释放(通常会引发这些对象的回收,从而减少了程序的内存占用量)。

自动释放池和线程

Cocoa应用程序中的每个线程都会维护一个自己的NSAutoreleasePool对象的堆栈。当一个线程终止时,它会自动地释放所有与自身相关的自动释放池。在基于Application Kit的应用程序中,自动释放池会在程序的主线程中被自动创建和销毁,所以,您的代码通常无需处理它们。但是,如果您在Application Kit的主线程之外发起Cocoa调用,则您需要创建自己的自动释放池。如果您正在编写一个Foundation应用程序,或者如果您拆分了一个线程,则是属于这种情况。

如果您的应用程序或线程是长期存在的,并且会潜在地生成大量的自动释放对象,那么您应该定期地销毁和创建自动释放池(像Application Kit在主线程中所做的那样),否则,自动释放对象会产生堆积且您的内存占用量也会增长。如果被拆分的线程不会发起Cocoa调用,那么您无需创建自动释放池。

注意:如果您使用POSIX线程API(而不是NSThread)创建从属线程,则您无法使用Cocoa—包括NSAutoreleasePool在内除非Cocoa处于多线程模式。Cocoa只有在拆分它的第一个NSThread对象之后才进入多线程模式。要在从属POSIX线程中使用Cocoa,您的应用程序必须首先至少拆分1个可以立即退出的NSThread对象。您可以使用NSThread类的isMultiThreaded方法测试Cocoa是否处于多线程模式。

自动释放池的作用域和嵌套自动释放池的含义

我们通常会提及自动释放池是被嵌套的,如清单1所示。但是,您也可以认为嵌套自动释放池位于一个堆栈中,其中,最内层的自动释放池位于栈顶。如前所述,嵌套自动释放池实际上是这样实现的:程序中的每个线程都维护一个自动释放池的堆栈。当您创建一个自动释放池时,它被压入当前线程的堆栈的栈顶。当一个对象被自动释放时也就是说,当一个对象收到一条autorelease消息或者当它作为一个参数被传入addObject:类方法时它总是被放入堆栈顶部的自动释放池中

因此,自动释放池的作用域是由它在堆栈中的位置以及它的存在情况定义的。自动释放对象被添加至栈顶的自动释放池中。如果另一个自动释放池被创建,则当前位于栈顶的池就超出其作用域,直到新的池被释放为止(此时原来的自动释放池再次成为栈顶的自动释放池)。当自动释放池本身被释放的时候,它(显然)就永久地超出其作用域。

如果您释放了一个不是位于堆栈顶部的自动释放池,则这会导致堆栈中所有位于它上面的(未释放的)自动释放池,连同它们包含的所有对象一起被释放。当您用完自动释放池时,如果您一时疏忽,忘记向它发送release消息(不推荐您这样做),那么,当嵌套在它外层的自动释放池中的某个被释放时,它也会被释放。

这种行为对于异常的处理很有意义。如果发生异常,并且线程突然转移出当前的上下文环境,则与该上下文相关联的自动释放池将被释放。但是,如果被释放的池不是线程堆栈顶部的池,则所有位于该自动释放池之上的自动释放池也会被释放(并在这个过程中释放其中所有的对象)。然后,先前位于被释放的池下面的自动释放池则成为线程堆栈最顶端的自动释放池。由于这种行为,异常处理程序则不需要释放收到autorelease消息的对象。对于异常处理程序来说,没有必要也不值得向它的自动释放池发送release,除非异常处理程序重新引发该异常。

保证基础所有权策略

通过创建一个自动释放池,而不是简单地释放对象,您可以将临时对象的生命周期延长至自动释放池的生命周期。在自动释放池被回收之后,您应该将该池有效期间被自动释放的任何对象视为已被销毁,而不应向这个对象发送消息或将它返回给方法的调用者。

如果您必须超出自动释放的上下文范围,使用其中的一个临时对象,您可以这样做:在该上下文范围内向这个对象发送一条retain消息,然后,在自动释放池被释放之后向它发送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 release];

}

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

}

subpool有效期间向match发送retain,并且在subpool被释放之后向match发送autorelease,如此一来,match就被有效地从subpool移至先前有效的池中。这样就延长了match的生命周期,并允许它接收循环之外的消息,而且被返回给findMatchingObject:的调用者。

垃圾回收

虽然垃圾回收系统(详见垃圾回收编程指南)不使用自动释放池本身,但是,如果您正在开发一种混合框架(即一种可以同时用于垃圾回收和引用计数环境的框架),自动释放池可以为回收者提供一些提示信息。

当您想要释放那些已经被添加到自动释放池中的对象的所有权时,自动释放池就要被释放。这往往会导致当时堆积的临时对象被销毁例如,在事件周期的结尾,或者在一个创建了大量临时对象的循环期间。一般来说,这些也是有助于提示垃圾回收者需要进行垃圾回收的时间点。

在垃圾回收环境中,release是一个空操作。因此,NSAutoreleasePool提供了drain方法,在引用计数环境中,该方法的作用等同于调用release,但在垃圾回收环境中,它会触发垃圾回收(如果自上次垃圾回收以来分配的内存大于当前的阈值)。因此,在通常情况下,您应该使用drain而不是release来销毁自动释放池。

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics