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!
This isn’t a problem with your autorelease approach. Your approach looks sound.
The problem is
logMessageas written is fundamentally flawed. It’s going to fail any time there’s a drain of an enclosing autorelease pool between invokations. The main run loop (modulo optimizations) drains its autorelease pool on every event loop spin, so this would fail there just as badly in that case.A couple of FWIWs:
can be written
and
[pool drain]is preferred over[pool release]