从 ShouldTerminate 返回 NSTerminateLater 后,NSApplicationDelegate 没有得到延迟调用?

NSApplicationDelegate not getting delayed call after returning NSTerminateLater from ShouldTerminate?

我在 Xcode 中创建了一个 Mac 应用程序。我所做的唯一代码更改是在应用程序委托中。现在,它的整体内容如下所示:

#import "AppDelegate.h"

@interface AppDelegate ()

@end

@implementation AppDelegate

- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
}

- (void) readyToTerminate: (id) sender {
  NSLog(@"Ready to terminate");
  [NSApplication.sharedApplication replyToApplicationShouldTerminate:YES];
}

- (NSApplicationTerminateReply) applicationShouldTerminate:(NSApplication *)sender {
  [self performSelector:@selector(readyToTerminate:) withObject:self afterDelay:1.0];
  NSLog(@"Later please");
  return NSTerminateLater;
}

- (void)applicationWillTerminate:(NSNotification *)aNotification {
  NSLog(@"Terminating");
}

@end

当我 运行 应用程序时,我确实看到 window。然后我按 command-Q 终止。输出为:

2018-04-01 17:20:03.110563-0500 TerminateThisApp[3336:364401] Unknown Window 
class (null) in Interface Builder file,
     creating generic Window instead
2018-04-01 17:20:06.929175-0500 TerminateThisApp[3336:364401] Later please

所以 readyToTerminate: 没有被调用。为什么会这样?没看懂。

为了它的价值,我尝试了一个具有相同非结果的 NSTimer。我还尝试以相同的方式从 didFinishLaunching 调用 readyToTerminate。在那种情况下,readyToTerminate 确实触发了(并且应用程序没有终止,这很好)。

这是怎么回事?

我刚刚使用你的代码在 Xcode 9.3 上构建了一个快速的 macOS 应用程序,并注意到选择器抛出了启动服务异常(增加你的超时时间以便在你单步执行代码时清楚地看到它在调试器中)。

如果您查看 Console 应用程序并按您的应用程序名称进行过滤(例如,我使用的是 SO-49603218),您会看到这样的条目吗:

default 09:44:00.031347 +1000   SO-49603218 LSExceptions shared instance invalidated for timeout.
default 09:44:55.906555 +1000   SO-49603218 Later please

我认为可能是应用程序引用了计时器所指的,即 self 正在失效。这可能与当您回复 NSTerminateLater

时 运行 循环切换到 NSModalPanelRunLoopMode 有关

如果您更改为 NSTerminateCancel,那么您的 readyToTerminate: 方法将被调用。

default 09:55:28.851246 +1000   SO-49603218 Later please
default 09:55:28.864566 +1000   SO-49603218 LSExceptions shared instance invalidated for timeout.
default 09:55:29.851891 +1000   SO-49603218 Ready to terminate

注意LSException还是会抛出

我认为在阅读了更多关于 Anatomy of a Run Loop documentation 的内容后,问题是 performSelector: 调用是在 NSDefaultRunLoopMode 上进行的,所以它不会被调用,直到你 return到 运行 模式。从文档中,在 Timer Sources:

Like input sources, timers are associated with specific modes of your run loop. If a timer is not in the mode currently being monitored by the run loop, it does not fire until you run the run loop in one of the timer’s supported modes.