I am getting an EXC_BAD_ACCESS error after I return from a thread method in which I have set up an NSAutoreleasePool. The place of failure is at a call to NSRunLoop. I am trying to wrap a 3rd party library consisting mainly of a class (let's call it the Connection class) and its delegate such that it will behave synchronously instead of asynchronously to client classes. The wrapping class, called NFCConnection, conforms to the delegate protocol and is passed to Connection's setDelegate method in NFCConnection's constructor.
The method in which I create the new thread is as follows:
- (void)methodA {
[NSThread detachNewThreadSelector:@selector(doSomethingImportant)
toTarget:self
withObject:nil];
while (!callBackInvoked) {
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode // Error!
beforeDate:[NSDate dateWithTimeIntervalSinceNow:1.0]];
}
}
The thread method, doSomethingImportant, looks like this:
- (void)doSomethingImportant {
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
[[Connection instance] somethingImportant];
[pool release];
}
The call to Connection's somethingImportant will cause one of the delegate methods to be called on the main thread. In each of these call back methods I set the variable callBackMethod to NO, causing the while loop in methodA to exit. The error happens after doSomethingImportant returns but before one of the delegate methods are called. The EXC_BAD_ACCESS error occurs at the call to NSRunLoop. Here is part of the stack trace:
#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
...
Now I can prevent the error from happening at all and the wrapped API seems to work synchronously if I neglect to set up the autorelease pool in doSomethingImportant. But if I were to do that then the following gets printed in the console:
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)
Are these messages above caused by something not being released in the the Connection class instance? I'm am creating an NSDate but not an instance of which that needs to be release. Same thing with an NSTimer.
I'm trying to do the right thing here by setting up the NSAutoreleasePool but it looks like I'm doing something wrong. However I don't have a clue as to what that might be. Any help much appreciated!
Rich
First, you do definitely need the new thread to have an autorelease pool, so don't punt on solving this. :-)
That said, this has some of the hallmarks of a premature release or an over-release somewhere in the code. I would pay particular attention to the Connection object's delegate, as the rules about an object retaining its delegate are somewhat slippery. (Cocoa objects in general do not retain their delegates, but third-party code may -- and sometimes for good reasons.)
I'll now call your attention to Tracking Memory Usage. Some permutation of MallocDebug and NSZombieEnabled=YES should eventually uncover the culprit code.
Once you're past this bug, though, you might want to explore Grand Central Dispatch for this sort of thing rather than rolling your own threads... Your code as it stands looks like it should probably be using an NSCondition or pthread_condition variable to be strictly correct. While you can get away with a lot on current hardware, these kinds of unsynchronized shared accesses can easily cause some really nasty races. GCD (AKA libdispatch) offers a much cleaner and more modern paradigm, so if you're going to learn something new it's a much better investment than pthreads / NSThread / etc. :-)