来自另一个线程的回调中NSAutoreleasePool的最佳实践

时间:2011-12-07 23:33:08

标签: objective-c multithreading cocoa autorelease nsautoreleasepool

我有一个C ++库,我希望将其作为Objective-C框架公开,因此对Objective-C开发人员来说更容易使用。在结束C ++库时,我遇到了一个处理自动释放对象和线程的特殊问题。

该库的一个特性是开发人员可以注册一个“记录器”,用于接收来自库的回调的通知消息。来自库的通知使用C ++类型并从另一个(POSIX)线程接收,因此我创建了一个私有C ++包装类来处理它:它接收回调,将char *参数转换为NSString,并传递给它到用户提供的Objective-C记录器实例。这一切都很好,看起来像这样:

// Is called from the C++ library from another posix thread
void ObjCLoggerWrapper::LogMessage(const char *message)
{
  NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
  // Pass string to the user-provided Objective-C instance called "Logger"
  [Logger logMessage:[[[NSString alloc] initWithUTF8String:message] autorelease]];
  [pool release];
}

作为用户回调的一个例子,我编写了这个简单的方法来收集用户类实例中的NSString成员m_text中的所有日志记录(在别处使用,但这是无关紧要的。)

-(void) logMessage: (NSString*)message
{
  @synchronized(self)
  {
    m_text = [m_text stringByAppendingFormat:@"%04d: %@\r\n", m_lineno++, message];
  }
}

到目前为止一切顺利。或者我想。但这是牛肉:

用户回调方法中的所有自动释放对象都属于包装器的NSAutoreleasePool,因此在回调完成时会被释放。

糟糕!这意味着我的m_text字符串(由stringByAppendingFormat消息隐式创建为自动释放的对象)将在logMessage完成后释放并变为僵尸。下次访问时,代码将崩溃。当然,用户肯定并且理所当然地不期望这样。在弄清楚发生了什么之前,我自己不得不挠头几次。

所以我的问题是:当从另一个线程回调用户代码时,我们应该如何处理自动释放的对象?

我看到了几种可能的选择。没有一个是完美的,谷歌没有帮助(因此这个问题)。

  1. 告诉用户“不要在回调代码中创建自动释放对象”。不好:这些对象通常是不自觉地创建的,例如通过stringByAppendingFormat和大量其他框架方法。除了稍后难以调试的崩溃之外,没有任何警告。
  2. 没有NSAutoreleasePool。如果用户尝试创建自动释放的对象,则缺少一个将导致警告。绝对不漂亮,但会以健壮的方式提醒用户注意问题。并且用户可以“只”添加他自己的NSAutoreleasePool来修复这种情况。但又一次:不漂亮。
  3. 没有NSAutoreleasePool并使用performSelectorOnMainThread在主线程上运行回调。任何新的自动释放对象都会在主线程的池中结束。我认为这是安全的,但欢迎评论 - 例如,总是可以在主线程上执行回调吗?这种方法需要在包装器中进行更精细的编码,以避免线程死锁并等待结果,但到目前为止,这是我的首选。
  4. 只是说清楚:重写我自己的包装是没问题的。我的主要优先事项是创建一个能够为Objective-C框架的用户平稳无缝地工作的解决方案。谢谢!

2 个答案:

答案 0 :(得分:2)

这不是您的自动释放方法的问题。你的方法听起来很健康 问题是logMessage因为书面存在根本缺陷。任何时候在调用之间都会消耗一个封闭的自动释放池,它就会失败。主运行循环(模优化)在每个事件循环旋转时消耗其自动释放池,因此在那种情况下这将失败就好了。

几个FWIW:

[Logger logMessage:[[[NSString alloc] initWithUTF8String:message] autorelease]];

可以写

[Logger logMessage:[NSString stringWithUTF8String:message]];

并且[pool drain]优先于[pool release]

答案 1 :(得分:1)

如果不确切知道你的图书馆做了什么,很难推荐一种方法。但是,调用 performSelectorOnMainThread 是一种相当安全的策略。鉴于您不希望日志消息需要立即执行操作,等待主线程执行您的回调应该没问题。