I'm attempting to debug some iOS crash logs that contain the following error message:
*** Terminating app due to uncaught exception 'NSDestinationInvalidException', reason: '*** -[SomeClass performSelector:onThread:withObject:waitUntilDone:modes:]: target thread exited while waiting for the perform
The relevant section of the code is:
- (void) runInvocationOnMyThread:(NSInvocation*)invocation {
NSThread* currentThread = [NSThread currentThread];
if (currentThread != myThread) {
//call over to the correct thread
[self performSelector:@selector(runInvocationOnMyThread:) onThread:myThread withObject:invocation waitUntilDone:YES];
}
else {
//we're okay to invoke the target now
[invocation invoke];
}
}
This is similar to the issue discussed here, except that I'm not trying to cancel my onThread:
thread. In fact, in my case onThread:
is being passed a reference to the application's main thread, so it should not be possible for it to terminate unless the entire app is terminating.
So the first question is, is the "target" thread referred to in the error message the one I'm passing to onThread:
, or the one that's waiting for the invocation to complete on the onThread:
thread?
I've assumed that it's the second option, as if the main thread really has terminated the crash of the background thread is kind of moot anyways.
With that in mind, and based upon the following discussion from the reference docs for performSelector:onThread:...
:
Special Considerations
This method registers with the runloop of its current context, and depends on that runloop being run on a regular basis to perform correctly. One common context where you might call this method and end up registering with a runloop that is not automatically run on a regular basis is when being invoked by a dispatch queue. If you need this type of functionality when running on a dispatch queue, you should use dispatch_after and related methods to get the behavior you want.
...I've modified my code to prefer the use of GCD over performSelector:onThread:...
, as follows:
- (void) runInvocationOnMyThread:(NSInvocation*)invocation {
NSThread* currentThread = [NSThread currentThread];
if (currentThread != myThread) {
//call over to the correct thread
if ([myThread isMainThread]) {
dispatch_sync(dispatch_get_main_queue(), ^{
[invocation invoke];
});
}
else {
[self performSelector:@selector(runInvocationOnMyThread:) onThread:myThread withObject:invocation waitUntilDone:YES];
}
}
else {
//we're okay to invoke the target now
[invocation invoke];
}
}
Which seems to work fine (though no idea if it fixes the crash, as it's an exceedingly rare crash). Perhaps someone can comment on whether this approach is more or less prone to crashing than the original?
Anyways, the main problem is that there's only an obvious way to use GCD when the target thread is the main thread. In my case, this is true, but I'd like to be able to use GCD regardless of whether or not the target thread is the main thread.
So the more important question is, is there a way to map from an arbitrary NSThread
to a corresponding queue in GCD? Ideally something along the lines of dispatch_queue_t dispatch_get_queue_for_thread(NSThread* thread)
, so that I can revise my code to be:
- (void) runInvocationOnMyThread:(NSInvocation*)invocation {
NSThread* currentThread = [NSThread currentThread];
if (currentThread != myThread) {
//call over to the correct thread
dispatch_sync(dispatch_get_queue_for_thread(myThread), ^{
[invocation invoke];
});
}
else {
//we're okay to invoke the target now
[invocation invoke];
}
}
Is this possible, or is there not a direct mapping from NSThread
to GCD queue that can be applied?