I have a Cocoa application (Mac OS X SDK 10.7) that runs some processes through Grand Central Dispatch (GCD). These processes manipulate some Core Data NSManagedObjects (non-document based) in a way that, in my opinion, is thread safe (creating a new managed control object for use in this thread).
Problem I have when the user tries to exit the application while the send queue is still running.
Before calling the delegate, NSApplication is invoked.
- (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication *)sender
I get the error message "Failed to merge changes." This is somewhat expected since operations are still being performed using various managed objects. Then I present NSAlert from a template generated using the main data application.
There is a section in the Thread Programming Guide entitled βBe aware of thread behavior at exit timeβ that talks about using replyToApplicationShouldTerminate:. I have small problems implementing this.
What I would like for my application to complete the processing of queued items and then complete without giving the user an error message. It would also be useful to update the view or use the leaflet so that the user can know that the application is performing an action and will stop when the action is completed.
Where and how to implement this behavior?
Solution : Therefore, I had several different problems.
I had blocks that accessed the main data in dispatch_queue
, which did not allow my application to stop the grace.
When I tried to add a new element to dispatch_queue, a new instance of dispatch_queue was launched in a new thread.
What I did to solve this problem is to use the NSNotificationCenter
in my AppDelegate
(where (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication *)sender
called). In the code for the template that Core Data generates, add the following:
// 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; }
Then in the AppController
add an observer for notification (I added this to awakeFromNib
):
- (void)awakeFromNib { NSNotificationCenter *center = [NSNotificationCenter defaultCenter]; [center addObserver:self selector:@selector(terminateApplicationFromQueue:) name:@"TerminateApplicationFromQueue" object:nil];
I also created a struct
that can be checked to see if the user wants to shut down the application. (I set the initial state of the structure to awakeFromNib
above). Put a struct
after the @synthesize
:
struct { bool isAppTerminating; bool isTerminatingNow; } appTerminating;
Now for a long-term dispatch_queue
that prevents the application from gracefully shutting down. When I initially create this dispatch_queue
, the for loop is used to add items that need updating. After this loop of the loop, I attached myself to another element of the queue, which will check the struct
to see if the application should end:
// 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);
And the method that will be called when a notification is received:
- (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); } }