Мягкое завершение NSApplication с помощью Core Data и Grand Central Dispatch (GCD)

У меня есть приложение Cocoa (Mac OS X SDK 10.7), которое выполняет некоторые процессы через Grand Central Dispatch. (НОД). Эти процессы манипулируют некоторыми Core Data NSManagedObjects (не-документами-основанными)способом, который я считаю потокобезопасным (, создавая новый управляемыйObjectContext для использования в этом потоке).

Проблема, с которой я сталкиваюсь, заключается в том, что пользователь пытается выйти из приложения, в то время как очередь отправки все еще работает.

Делегат NSApplication вызывается перед выходом.

- (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication *)sender 

Я получаю сообщение об ошибке «Не удалось объединить изменения». Что несколько ожидаемо, так как все еще выполняются операции через другой управляемыйObjectContext. Затем я получаю NSAlert из шаблона, созданного с помощью основного приложения данных.

В «Руководстве по программированию потоков» есть раздел «Учитывайте поведение потоков во время выхода», в котором упоминается использование метода replyToApplicationShouldTerminate :. У меня небольшие проблемы с реализацией этого.

Я бы хотел , чтобы мое приложение завершило обработку элементов очереди, а затем завершило работу без предоставления пользователю сообщения об ошибке. Также было бы полезно обновить представление или использовать лист, чтобы сообщить пользователю, что приложение выполняет какое-то действие и завершится, когда действие будет завершено.

Где и как реализовать такое поведение?

Решение: Здесь у меня было несколько разных проблем.

  1. У меня были блоки, которые обращались к основным данным в dispatch_queue, препятствуя корректному завершению моего приложения.

  2. Когда я попытался добавить новый элемент в очередь отправки_, в новом потоке был запущен новый экземпляр очереди отправки_.

Чтобы решить эту проблему, я использовал 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;
}

Затем в 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);
    }
}

7
задан Cœur 30 March 2019 в 11:49
поделиться