使用核心数据和Grand Central Dispatch(GCD)优雅终止NSApplication

Vin*_*Mac 7 macos cocoa objective-c

我有一个Cocoa应用程序(Mac OS X SDK 10.7),它通过Grand Central Dispatch(GCD)执行某些过程.这些进程以我认为是线程安全的方式操作某些Core Data NSManagedObjects(非基于文档的)(创建一个新的managedObjectContext以供在此线程中使用).

我遇到的问题是当用户在调度队列仍在运行时尝试退出应用程序时.

在实际退出之前调用NSApplication委托.

- (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication *)sender 
Run Code Online (Sandbox Code Playgroud)

我收到错误"无法合并更改".由于仍然通过不同的managedObjectContext执行操作,因此有些预期.然后,我将从使用核心数据应用程序生成的模板中获取NSAlert.

在" 线程编程指南"中有一节名为"在退出时意识到线程行为",其中提到使用replyToApplicationShouldTerminate:方法.我在实现这个方面遇到了一些麻烦.

我想要的是我的应用程序完成处理排队的项目,然后终止而不向用户显示错误消息.更新视图或使用工作表让用户知道应用程序正在执行某些操作并在操作完成时终止也会很有帮助.

我将在何处以及如何实现此行为?

解决方案:所以我在这里遇到了一些不同的问题.

  1. 我有阻止访问核心数据的块,以dispatch_queue防止我的应用程序正常终止.

  2. 当我尝试向dispatch_queue添加新项时,在新线程上启动了dispatch_queue的新实例.

我在解决这个问题上所做的就是NSNotificationCenter在我的AppDelegate((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;
}
Run Code Online (Sandbox Code Playgroud)

然后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;
}
Run Code Online (Sandbox Code Playgroud)

我还创建了一个struct可以检查的用户,以查看用户是否要终止该应用程序.(我在awakeFromNib上面设置了结构的初始状态).放在struct你的@synthesize陈述之后:

struct {
    bool isAppTerminating;
    bool isTerminatingNow;
} appTerminating;
Run Code Online (Sandbox Code Playgroud)

现在为长期运行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);
Run Code Online (Sandbox Code Playgroud)

以及收到通知时要调用的方法:

- (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);
    }
}
Run Code Online (Sandbox Code Playgroud)

Jos*_*ell 1

我自己没有处理过这个问题,但仅从我对文档的阅读来看,您应该做的是:

  1. NSTerminateLater从回来applicationShouldTerminate:这让系统知道您的应用程序尚未准备好终止,但很快就会终止。
  2. 将“最终”块放入调度队列中。(您需要确保在此之后其他块没有排队。执行所有其他工作后将运行该块。请注意,队列必须是串行的 - 而不是并发队列之一)才能正常工作.)“最终”块应该执行[NSApp replyToApplicationShouldTerminate:YES];,这将完成正常的终止过程。

没有任何直接的方法可以确定 GCD 队列是否仍在工作。您可以做的(据我所知)处理此问题的唯一另一件事是将所有块放入调度组中,然后等待该组applicationShouldTerminate:(使用dispatch_group_wait().