objective-c - 使用 Core Data 和 Grand Central Dispatch (GCD) 优雅终止 NSApplication

标签 objective-c macos cocoa

我有一个 Cocoa 应用程序 (Mac OS X SDK 10.7),它正在通过 Grand Central Dispatch (GCD) 执行一些进程。这些进程正在以我认为线程安全的方式操作一些核心数据 NSManagedObjects(非基于文档的)(创建一个新的 managedObjectContext 用于该线程)。

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

NSApplication 委托(delegate)在实际退出之前被调用。

- (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication *)sender 

我收到错误“无法合并更改”。这在某种程度上是意料之中的,因为仍然有一些操作正在通过不同的 managedObjectContext 执行。然后我看到了来自核心数据应用程序生成的模板的 NSAlert。

Threading Programming Guide有一个名为“退出时注意线程行为”的部分暗示使用 replyToApplicationShouldTerminate:方法。我在实现这个时遇到了一些麻烦。

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

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

解决方案: 所以我在这里遇到了几个不同的问题。

  1. 我有 block 访问 dispatch_queue 中的核心数据,阻止我的应用程序正常终止。

  2. 当我尝试向 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);
    }
}

最佳答案

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

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

没有任何直接的方法可以查明 GCD 队列是否仍在工作。您唯一可以做的(据我所知)来处理这个问题是将所有 block 放入 dispatch group 中。 ,然后在 applicationShouldTerminate: 中等待组(使用 dispatch_group_wait()

关于objective-c - 使用 Core Data 和 Grand Central Dispatch (GCD) 优雅终止 NSApplication,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/10081123/

相关文章:

python - 将日期时间从 Objective C 传递到 Django

ios - 取出过滤后的数据,再次过滤Coredata中的数据

regex - 在 mac 上用 sed 中的字符串替换换行符

objective-c - 使用 NSDateComponents : changing year 获取一周的第一天

regex - 为什么 echo 'hello world' | awk '/hello\s/{print $0}' 什么都不产生?

linux - ssh 协议(protocol)差异,显然在 ssh 6.x 和 5.x 之间

objective-c - OSX - NSLog 防止应用程序在 Debug模式下崩溃

swift - 在多个弹出窗口和 Storyboard之间共享弹出按钮菜单?

objective-c - 在哪里定义了像 NSControlKeyMask 这样的标识符

ios - 带有颜色渐变的 UIBezierPath