我有一个C++库,希望将其作为Objective-C框架公开,以便Objective-C开发人员更容易使用。在封装C++库时,我遇到了一个特定的问题,涉及autorelease对象和线程。
该库的一个特性是,开发人员可以注册“日志记录器”来接收来自库的回调通知消息。来自库的通知使用C++类型,并从另一个(POSIX)线程接收,因此我编写了一个私有的C++包装类来处理这个问题:它接收回调,将char *参数转换为NSString,并将其传递给用户提供的Objective-C日志记录器实例。这一切都非常顺利,看起来像这样:
到目前为止,一切看起来都很好。或者我这样认为。但问题在于:所有在用户回调方法中的自动释放对象都将属于包装器的NSAutoreleasePool,并在回调完成时被释放。
糟糕!这意味着我的m_text字符串,由stringByAppendingFormat消息隐式创建为自动释放对象,将在logMessage完成时被释放并变成僵尸。下次访问时,代码会崩溃。当然,用户肯定并且应该不期望出现这种情况。我自己也不得不多次思考才能意识到发生了什么。
因此,我的问题是:当从另一个线程回调到用户代码时,我们应该如何处理自动释放对象?
我看到几种可能的选择。没有一种是完美的,而且谷歌也没有帮助(因此提出了这个问题)。
1. 告诉用户“不要在回调代码中创建自动释放对象”。这不好:这些对象经常是无意中创建的,例如通过stringByAppendingFormat和大量其他框架方法。除了稍后难以调试的崩溃之外,没有任何警告。
2. 没有NSAutoreleasePool。如果用户尝试创建自动释放对象,则缺少警告。绝对不漂亮,但会以强大的方式警告用户存在问题。用户可以“只是”添加自己的NSAutoreleasePool来解决问题。但同样:不漂亮。
3. 没有NSAutoreleasePool,并使用performSelectorOnMainThread在主线程上运行回调。任何新的自动释放对象都将出现在主线程的池中。我认为这是安全的,但欢迎评论-例如,回调总是可以在主线程上执行吗?这种方法需要包装器中更精细的编码以避免线程死锁并等待结果,但到目前为止,这是我首选的选择。
只是为了让它清楚:重写我的包装器没有问题。我的主要优先事项是创建一个对Objective-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在主线程上运行回调。任何新的自动释放对象都将出现在主线程的池中。我认为这是安全的,但欢迎评论-例如,回调总是可以在主线程上执行吗?这种方法需要包装器中更精细的编码以避免线程死锁并等待结果,但到目前为止,这是我首选的选择。
只是为了让它清楚:重写我的包装器没有问题。我的主要优先事项是创建一个对Objective-C框架的用户平稳无缝工作的解决方案。谢谢!
m_text
是一个实例变量,对吧?我不明白为什么有人会认为m_text = <autoreleased object>
是安全的。我不会这样做。如果我真的需要做类似的事情,我会使用类似于m_text = [...[m_text autorelease] ... retain];
的方法。 - smparkes