У меня есть приложение Cocoa (Mac OS X SDK 10.7), которое выполняет некоторые процессы через Grand Central Dispatch. (НОД). Эти процессы манипулируют некоторыми Core Data NSManagedObjects (не-документами-основанными)способом, который я считаю потокобезопасным (, создавая новый управляемыйObjectContext для использования в этом потоке).
Проблема, с которой я сталкиваюсь, заключается в том, что пользователь пытается выйти из приложения, в то время как очередь отправки все еще работает.
Делегат NSApplication вызывается перед выходом.
- (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication *)sender
Я получаю сообщение об ошибке «Не удалось объединить изменения». Что несколько ожидаемо, так как все еще выполняются операции через другой управляемыйObjectContext. Затем я получаю NSAlert из шаблона, созданного с помощью основного приложения данных.
В «Руководстве по программированию потоков» есть раздел «Учитывайте поведение потоков во время выхода», в котором упоминается использование метода replyToApplicationShouldTerminate :. У меня небольшие проблемы с реализацией этого.
Я бы хотел , чтобы мое приложение завершило обработку элементов очереди, а затем завершило работу без предоставления пользователю сообщения об ошибке. Также было бы полезно обновить представление или использовать лист, чтобы сообщить пользователю, что приложение выполняет какое-то действие и завершится, когда действие будет завершено.
Где и как реализовать такое поведение?
Решение: Здесь у меня было несколько разных проблем.
У меня были блоки, которые обращались к основным данным в 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;
}
Затем в 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);
}
}