Graceful completion of NSApplication with master data and Grand Central Dispatch (GCD)

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]; // Set initial state of struct that dispatch_queue checks to see if it should terminate the application. appTerminating.isAppTerminating = NO; appTerminating.isTerminatingNow = NO; } 

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); } } 
+6
source share
1 answer

I did not do this myself, but just from my reading of the docs, it looks like you should do this:

  • Return NSTerminateLater from applicationShouldTerminate: This allows the system to know that your application is not yet ready for completion, but do so soon.
  • Complete the "final" block in the send queue. (You must make sure that no other blocks will be allocated after that. Then this block will be launched after all other work is done. Please note that the queue must be sequential and not one of the parallel queues) for this to work correctly.) The "final" block should do [NSApp replyToApplicationShouldTerminate:YES]; which will complete the normal completion process.

There is no direct way to find out if the GCD queue is working. The only thing you can do (what I know) for this is to put all the blocks in the dispatch group , and then wait in the applicationShouldTerminate: group (using dispatch_group_wait() .

0
source

Source: https://habr.com/ru/post/912798/


All Articles