I have a C++ library that I want to expose as an Objective-C framework, so it will be easier to use for Objective-C developers. In wrapping up the C++ library I have come across one particular problem in dealing with autorelease objects and threading.
One feature of the library is that the developer can register a "logger" for receiving notification-messages as callbacks from the library. The notification from the library use C++ types and is received from another (POSIX) thread, so I've made a private C++ wrapper class to handle this: it receives the callback, turn the char* argument into an NSString, and pass it on to an Objective-C logger instance provided by the user. This all works very well and looks something like this:
// 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];
}
As an example of a user callback I wrote this simple method to collect all logging in a NSString member m_text in the user-class' instance (to be used elsewhere, but that's irrelevant).
-(void) logMessage: (NSString*)message
{
@synchronized(self)
{
m_text = [m_text stringByAppendingFormat:@"%04d: %@\r\n", m_lineno++, message];
}
}
So far so good. Or so I thought. But here's the beef:
All autoreleased objects in the user's callback method will belong to the wrapper's NSAutoreleasePool and hence be released when the callback is completed.
Oops! This means that my m_text string, created implicitly as an autoreleased object by the stringByAppendingFormat message, will be released when logMessage is done and become a zombie. When accessed the next time, the code will crash. The user, of course, certainly and rightfully does not expect this. I myself had to scratch my head a few times before realizing what was going on.
So my question is: How should we deal with autoreleased objects when doing callbacks to user-code from another thread?
I see several possible options. None are perfect and google doesn't help (hence this question).
- Tell the user "don't create autorelease objects in your callback code". Not good: such objects are often created involuntarily, eg by stringByAppendingFormat and tons of other framework methods. There's no warning other than a later, hard-to-debug crash.
- Don't have an NSAutoreleasePool. The lack of one will cause warnings if the user tries to create autoreleased objects. Definitely not pretty, but will alert the user to the problem in a robust manner. And the user could "just" add an NSAutoreleasePool of his own to fix the situation. But again: not pretty.
- No NSAutoreleasePool and run the callback on the main thread using performSelectorOnMainThread. Any new autorelease objects would wind up on the main thread's pool. I think this is safe but would welcome comments - can callbacks always be performed on the main thread, for instance? This approach requires more delicate coding in the wrapper to avoid thread-deadlocks and to wait for the result, but it's my preferred choice so far.
Just to make it clear: Rewriting my own wrapper is no problem. My main priority is creating a solution that will work smooth and seamlessly for a user of the Objective-C framework. Thanks!
m_text
is an ivar, right? It's not clear to me why anyone would think thatm_text = <autoreleased object>
is safe. I wouldn't. If I really needed to do something like that, I'd use something along the lines ofm_text = [...[m_text autorelease] ... retain];
– smparkes