现代iOS 8中,NSOperation使用是否仍需要@autoreleasepool?

25

我已阅读 并发编程指南

在该指南中,文本说明GCD调度队列定义了它们自己的@autoreleasepool池,并提到仍然建议在每个调度级别上定义一个池,但是对于NSOperation,则没有任何说明,苹果提供的示例代码也没有显示使用@autoreleasepool结构。唯一涉及NSOperation的@autoreleasepool的模糊提及是在“修订历史”中:

  

2012年7月17日-删除与操作一起使用自动释放池的过时信息。

查看在线可用的示例代码(例如http://www.raywenderlich.com/19788/how-to-use-nsoperations-and-nsoperationqueues),在NSOperations对象的实现中使用@autoreleasepool,例如:

@implementation ImageDownloader

- (void)main {
    @autoreleasepool {
      ...
    }
}     
@end
  1. 我应该如何实现现代化的NSOperation对象?
  2. 苹果在2012-07-17提到的更新是什么?

3
一个自动释放池虚拟上毫无用处的地方就是在你的主程序周围。它只起到消除“仅泄漏”的警告信息,而实际上并不做任何事情。 - Hot Licks
1个回答

37

如果你从NSOperation派生并实现了main方法,则不需要设置自动释放池。 start方法的默认实现会推入一个NSAutoReleasePool,调用main,然后随即弹出NSAutoReleasePool。同样适用于NSInvocationOperationNSBlockOperation,它们共享start方法的同一实现。

下面是NSOperationstart方法的简略反汇编代码。请注意对NSPushAutoreleasePool的调用,然后是对main的调用,最后是对NSPopAutoreleasePool的调用:

Foundation`-[newMyObj__NSOperationInternal _start:]:
0x7fff8e5df30f:  pushq  %rbp

...

0x7fff8e5df49c:  callq  *-0x16b95bb2(%rip)        ; (void *)0x00007fff8d9d30c0: objc_msgSend
0x7fff8e5df4a2:  movl   $0x1, %edi

; new NSAutoreleasePool is pushed here
0x7fff8e5df4a7:  callq  0x7fff8e5df6d6            ; NSPushAutoreleasePool

... NSOperation main is called

0x7fff8e5df6a4:  callq  *-0x16b95dba(%rip)        ; (void *)0x00007fff8d9d30c0: objc_msgSend
0x7fff8e5df6aa:  movq   %r15, %rdi

; new NSAutoreleasePool is popped here, which releases any objects added in the main method
0x7fff8e5df6ad:  callq  0x7fff8e5e1408            ; NSPopAutoreleasePool

以下是一些示例代码运行的快照:

  1. MyObjmain 方法中被分配,并确保该对象必须被自动释放。
  2. main 返回到 _start,下面的图片显示了一个堆栈跟踪,其中 MyObj dealloc 由当前的自动释放池调用,在 _start 中弹出。

call stack showing object is released when NSPopAutoreleasePool is called

供参考,这是我用来验证此行为的示例代码:

#import <Foundation/Foundation.h>

@interface MyObj : NSObject
@end

@implementation MyObj

- (void)dealloc {
    NSLog(@"dealloc");
}

@end

@interface TestOp : NSOperation {
    MyObj *obj;
}

@end

@implementation TestOp

- (MyObj *)setMyObj:(MyObj *)o {
    MyObj *old = obj;
    obj = o;
    return old;
}

- (void)main {
    MyObj *old = [self setMyObj:[MyObj new]];
    [self setMyObj:old];
}

@end

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // insert code here...
        NSLog(@"Hello, World!");

        NSOperationQueue *q = [NSOperationQueue new];
        TestOp *op = [TestOp new];
        [q addOperation:op];

        [op waitUntilFinished];
    }
    return 0;
}

根据并发编程指南,Grand Central Dispatch同样管理调度队列的自动释放池:

如果您的块创建了超过少量的Objective-C对象,则可能需要将块代码的某些部分封装在@autorelease块中,以便处理这些对象的内存管理。虽然GCD调度队列有它们自己的自动释放池,但它们不能保证何时将这些池排空。如果您的应用程序受内存约束,那么创建自己的自动释放池可以让您更定期地释放自动释放对象的内存。


2
比我期望的要好!谢谢! - jrturton
2
我还应该补充一点,这不仅适用于 NSOperation,也适用于 NSBlockOperationNSInvocationOperation - Stuart Carnie
4
干得好!我已经更新了教程(http://www.raywenderlich.com/76341/use-nsoperation-nsoperationqueue-swift),去掉了自动释放池。我会尽快颁发赏金。 - jrturton
顺便问一下,你用的是什么主题?看起来很棒! - erdekhayser
我真的很喜欢这个详细的回答 +1! - Oscar Gomez
谢谢。你是怎么获取汇编转储的? - Maxim Veksler

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接