【发布时间】:2019-03-30 19:49:42
【问题描述】:
我有一个通过 Grand Central Dispatch (GCD) 执行某些进程的 Cocoa 应用程序 (Mac OS X SDK 10.7)。这些进程正在以我认为是线程安全的方式操作一些核心数据 NSManagedObjects(非基于文档)(创建一个新的 managedObjectContext 以在该线程中使用)。
问题我遇到的问题是当调度队列仍在运行时用户尝试退出应用程序。
NSApplication 委托在实际退出之前被调用。
- (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication *)sender
我收到“无法合并更改”错误。这在某种程度上是意料之中的,因为仍然有通过不同的 managedObjectContext 执行的操作。然后,我会看到使用核心数据应用程序生成的模板中的 NSAlert。
在Threading Programming Guide 中有一个名为“在退出时注意线程行为”的部分,暗示使用replyToApplicationShouldTerminate: 方法。我在实现这一点时遇到了一些麻烦。
我想要的是让我的应用程序完成对排队项目的处理,然后在不向用户显示错误消息的情况下终止。更新视图或使用工作表让用户知道应用正在执行某些操作并在操作完成时终止也会很有帮助。
我将在何处以及如何实现此行为?
解决方案: 所以我在这里遇到了一些不同的问题。
我的块正在访问
dispatch_queue中的核心数据,阻止我的应用程序正常终止。当我尝试向 dispatch_queue 添加新项目时,dispatch_queue 的新实例在新线程上启动。
我解决这个问题的方法是在我的AppDelegate 中使用NSNotificationCenter(其中(NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication *)sender 被调用。在Core Data 生成的模板代码中添加以下内容:
// Customize this code block to include application-specific recovery steps.
if (error) {
// Do something here to add queue item in AppController
[[NSNotificationCenter defaultCenter] postNotificationName:@"TerminateApplicationFromQueue" object:self];
return NSTerminateLater;
}
然后在AppController 中为通知添加观察者(我将其添加到awakeFromNib):
- (void)awakeFromNib {
NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
[center addObserver:self selector:@selector(terminateApplicationFromQueue:) name:@"TerminateApplicationFromQueue" object:nil];
// Set initial state of struct that dispatch_queue checks to see if it should terminate the application.
appTerminating.isAppTerminating = NO;
appTerminating.isTerminatingNow = NO;
}
我还创建了一个struct,可以检查用户是否想要终止应用程序。 (我在上面的awakeFromNib 中设置了结构的初始状态)。将struct 放在@synthesize 语句之后:
struct {
bool isAppTerminating;
bool isTerminatingNow;
} appTerminating;
现在是长时间运行的dispatch_queue,它会阻止应用正常终止。当我最初创建这个dispatch_queue 时,使用了一个 for 循环来添加需要更新的项目。执行此 for 循环后,我添加了另一个队列项,它将检查 struct 以查看应用程序是否应该终止:
// Additional queue item block to check if app should terminate and then update struct to terminate if required.
dispatch_group_async(refreshGroup, trackingQueue, ^{
NSLog(@"check if app should terminate");
if (appTerminating.isAppTerminating) {
NSLog(@"app is terminating");
appTerminating.isTerminatingNow = YES;
}
});
dispatch_release(refreshGroup);
以及收到通知时要调用的方法:
- (void)terminateApplicationFromQueue:(NSNotification *)notification {
// Struct to check against at end of dispatch_queue to see if it should shutdown.
if (!appTerminating.isAppTerminating) {
appTerminating.isAppTerminating = YES;
dispatch_queue_t terminateQueue = dispatch_queue_create("com.example.appname.terminate", DISPATCH_QUEUE_SERIAL); // or NULL
dispatch_group_t terminateGroup = dispatch_group_create();
dispatch_group_async(terminateGroup, terminateQueue, ^{
NSLog(@"termination queued until after operation is complete");
while (!appTerminating.isTerminatingNow) {
// add a little delay before checking termination status again
[NSThread sleepForTimeInterval:0.5];
}
NSLog(@"terminate now");
[NSApp replyToApplicationShouldTerminate:YES];
});
dispatch_release(terminateGroup);
}
}
【问题讨论】:
-
澄清一下:您是从
applicationShouldTerminate:返回NSTerminateLater,如replyToApplicationShouldTerminate:文档中所述? -
不,我不是。我不知道在哪里添加这个。
-
那你在返回什么?
-
现在它只是核心数据生成的默认样板。所以它会弹出一个警报,上面写着“退出时无法保存更改。还是退出?”如果用户选择“仍然退出”,则返回 NSTerminateNow,否则返回 NSTerminateCancel。
-
如果您不能立即终止,您应该设置您需要的任何内部状态,以向应用程序的其他部分发出信号,告知它们需要关闭并返回
NSTerminateLater。在模板代码中,您可以将其作为-applicationShouldTerminate:中的唯一内容执行,或者在if (!__managedObjectContext) ...部分之后执行。然后,当您的关闭进程完成后,您在主线程上调用-replyToApplicationShouldTerminate:。
标签: objective-c macos cocoa