使用 NSAutoreleasePool 从线程方法返回后 NSRunLoop 出错

Posted

技术标签:

【中文标题】使用 NSAutoreleasePool 从线程方法返回后 NSRunLoop 出错【英文标题】:Error at NSRunLoop after returning from thread method with NSAutoreleasePool 【发布时间】:2010-08-09 22:04:25 【问题描述】:

从设置了 NSAutoreleasePool 的线程方法返回后,我收到 EXC_BAD_ACCESS 错误。失败的地方是调用 NSRunLoop。我正在尝试包装一个主要由一个类(我们称其为 Connection 类)及其委托组成的第 3 方库,以便它与客户端类同步而不是异步运行。名为NFCConnection 的包装类符合委托协议,并在NFCConnection 的构造函数中传递给Connection 的setDelegate 方法。

我创建新线程的方法如下:

- (void)methodA 
    [NSThread detachNewThreadSelector:@selector(doSomethingImportant) 
                             toTarget:self 
                           withObject:nil];

    while (!callBackInvoked) 
        [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode     // Error!
                                 beforeDate:[NSDate dateWithTimeIntervalSinceNow:1.0]];        
    

线程方法 doSomethingImportant 如下所示:

- (void)doSomethingImportant 
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];    
    [[Connection instance] somethingImportant];    
    [pool release];

对 Connection 的 somethingImportant 的调用将导致在主线程上调用委托方法之一。在每个回调方法中,我将变量 callBackMethod 设置为 NO,导致方法 A 中的 while 循环退出。该错误发生在 doSomethingImportant 返回之后但在调用委托方法之一之前。 EXC_BAD_ACCESS 错误发生在调用 NSRunLoop 时。这是堆栈跟踪的一部分:

#0  0x3138cec0 in objc_msgSend
#1  0x000100ac in -[Connection ProcessRx_Api_SomethingImportant:] at Connection.m:621
#2  0x000114fa in +[Connection ProcessRx:] at Connection.m:900
#3  0x00012758 in -[CommHandling ProcessRx:length:] at CommHandling.m:294
#4  0x000126b8 in -[CommHandling stream:handleEvent:] at CommHandling.m:331
#5  0x30a7b958 in -[EAInputStream _streamEventTrigger]
#6  0x30a7be78 in __streamEventTrigger
#7  0x323f53a6 in CFRunLoopRunSpecific
#8  0x323f4c1e in CFRunLoopRunInMode
#9  0x3373c966 in -[NSRunLoop(NSRunLoop) runMode:beforeDate:]
#10 0x0000ae66 in -[NFCConnection methodA:] at NFCConnection.m:137
#11 0x0000bbf2 in -[NFCTestViewController doIt] at NFCTestViewController.m:39
...

现在,如果我忽略在 doSomethingImportant 中设置自动释放池,我可以完全防止错误发生,并且包装的 API 似乎可以同步工作。但如果我这样做,那么控制台会打印以下内容:

2010-08-09 14:54:49.259 ConnetionTest[3353:652f] *** _NSAutoreleaseNoPool(): Object 0x1928b0 of class __NSCFDate autoreleased with no pool in place - just leaking
Stack: (0x3374ff83 0x33723973 0x3372393f 0x323f78f1 0x3372b913 0x10221 0xc833 0xb045 0x33731acd 0x336dfd15 0x33ad8788)
2010-08-09 14:54:49.272 ConnetionTest[3353:652f] *** _NSAutoreleaseNoPool(): Object 0x18f800 of class NSCFTimer autoreleased with no pool in place - just leaking
Stack: (0x3374ff83 0x33723973 0x3372393f 0x3372b93b 0x10221 0xc833 0xb045 0x33731acd 0x336dfd15 0x33ad8788)

上面的这些消息是由 Connection 类实例中没有释放的东西引起的吗?我正在创建一个 NSDate 但不是需要发布的实例。 NSTimer 也是如此。

我正在尝试通过设置 NSAutoreleasePool 来做正确的事情,但看起来我做错了什么。但是,我不知道那可能是什么。非常感谢任何帮助!

丰富

【问题讨论】:

【参考方案1】:

首先,你确实需要新线程有一个自动释放池,所以不要急于解决这个问题。 :-)

也就是说,这具有过早发布或代码中某处过度发布的一些特征。我会特别注意 Connection 对象的委托,因为关于保留其委托的对象的规则有些模糊。 (Cocoa 对象通常保留它们的委托,但第三方代码可能 - 有时是有充分理由的。)

我现在请您注意Tracking Memory Usage。 MallocDebug 和 NSZombieEnabled=YES 的一些排列应该最终会发现罪魁祸首。

不过,一旦您克服了这个错误,您可能想要探索 Grand Central Dispatch 来解决这类事情,而不是滚动您自己的线程...您的代码看起来应该使用 NSCondition 或pthread_condition 变量要严格正确。虽然您可以在当前硬件上逃脱很多,但这些类型的不同步共享访问很容易导致一些非常讨厌的比赛。 GCD (AKA libdispatch) 提供了一个更清洁、更现代的范例,所以如果你要学习新东西,它是比 pthreads / NSThread / 等更好的投资很多。 :-)

【讨论】:

感谢凯林的回复。我已经考虑过 GCD,但它仅在 ios 4 中可用,并且示例中的这个 3rd 方库被编译为 3.x。我将阅读跟踪内存使用情况一文,并研究替代同步策略,看看我能找到什么。

以上是关于使用 NSAutoreleasePool 从线程方法返回后 NSRunLoop 出错的主要内容,如果未能解决你的问题,请参考以下文章

iPhone 开发多线程,NSAutoreleasePool

iPhone 上的 NSAutoreleasePool 问题

我如何找到因为缺少 NSAutoreleasePool 而泄漏的线程?

目标 C - NSthread 和 NSAutoreleasePool?

如何在 AppleScriptObjC 中使用 NSAutoreleasePool

Objective-c 中 NSAutoreleasePool 中的对象