Objective-C completion block causing extra method call?
Here's a weird one. My application sends a shutdown message to an object controlling a hardware device, with a completion block as an argument. The shutdown message returns a BOOL, depending on whether it was able to complete shutdown immediately. So YES means it's done now, NO means it will invoke the completion handler when it's done later.
Here's the main controller code:
- (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication *)sender
{
BOOL shutdownNow = [theStoker shutdownWithCompletionHandler:^(void)
{
NSLog(@"applicationShouldTerminate: completionBlock");
[[NSRunningApplication currentApplication] terminate];
}];
if (!shutdownNow)
{
NSLog(@"applicationShouldTerminate: waiting for shutdown");
return NSTerminateCancel;
}
return NSTerminateNow;
}
Here's the device controller code:
- (BOOL)shutdownWithCompletionHandler:(void (^)(void))handler
{
if (busy)
{
self.completionBlock = handler;
[self stopDevice];
NSLog(@"shutdownWithCompletionHandler: Wait for reset");
return NO;
}
NSLog(@"Stoker: shutdownWithCompletionHandler: shutdown now");
return YES;
}
The weird part is that the shutdown messages to be sent twice. These are the NSLog messages:
shutdownWi开发者_C百科thCompletionHandler: Wait for reset
applicationShouldTerminate: waiting for reset
applicationShouldTerminate: completionBlock
shutdownWithCompletionHandler: shutdown now
So after the completion block runs, I end up back in the device object's shutdownWithCompletionHandler:
method. Why?
Edit: Hmmm. Does the [[NSRunningApplication currentApplication] terminate];
cause the
applicationShouldTerminate:
to get called again? I think it must. Is there a better way to exit the app in the completion handler?
Since you have found the underlying cause of your problem (calling terminate
causes applicationShouldTerminate:
), here is how to avoid it.
Instead of canceling the termination, return NSTerminateLater
when it will shutdown later. Then, your completion block should call [NSApp replyToApplicationShouldTerminate:YES]
to trigger the termination, without calling applicationShouldTerminate:
again.
- (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication *)sender {
BOOL shutdownNow = [theStoker shutdownWithCompletionHandler:^(void) {
NSLog(@"applicationShouldTerminate: completionBlock");
[NSApp replyToApplicationShouldTerminate:YES];
}];
if (!shutdownNow) {
NSLog(@"applicationShouldTerminate: waiting for shutdown");
return NSTerminateLater;
}
return NSTerminateNow;
}
精彩评论