I want to terminate all currently called methods and then simply re-init my current object.
So when the user clicks on say the Start button, I call a bunch of methods...start doing stuff. Then when the user clicks on Cancel, i need to simply stop all running tasks/methods and re-init.
I don't want the app itself to terminate. So cannot use [NSApp terminate:nil]. I just need all current functions to stop and then i can call [self init];
Thanks in advance.
First, you have to set some flag and disable your user interface until all methods are completed. For example, self.cancelled = YES.
Next, each of your methods should be adjusted to return if that flag is set:
- (void)doSomething
{
if (self.cancelled) return;
// ...
for (/* ... */) {
// ...
if (self.cancelled) return;
// ...
}
// etc...
}
Then, you need a mechanism to track all your methods. Most likely, you will use NSOperationQueue or dispatch_queue_t. Take a look into documentation to find out how to get a notification when the queue becomes empty.
Finally, reset the self.cancelled flag and unblock your user interface.
Related
I'm using NSOperationQueue and a subclass of NSOperation for a part in my app that is generating a lot of data and therefore is very calculation-heavy.
When the app is closed by the user processingQueue.cancelAllOperations() is called. Also in my NSOperation subclass I overwrote cancel() to let it forward a cancel request to the class that does the actual heavy lifting ...
override func cancel() {
AppDelegate.instance.cancelDataGeneration()
super.cancel()
}
But this is still not enough. When I close the app while the data generation is ongoing it will crash in Xcode.
What can I do to prevent the crashing (which might result in data loss)? Is it OK to let the app wait for closing until all concurrent operations are canceled and how is this done (if it's even possible)? Or what other methods are generally used to address this issue?
UPDATE:
After more investigation I found that cancel() on my NSOperation subclass is never called, even after calling processingQueue.cancelAllOperations() in applicationShouldTerminate. So I added a method to manually call cancel on it:
func cancelDataGeneration() {
if let op = AppDelegate.instance._operation {
op.cancel();
}
}
And I call this from inside applicationShouldTerminate (since applicationShouldTerminate is called earlier than applicationWillTerminate. Interestingly, since my AppDelegate is a Singleton I have to use AppDelegate.instance._operation. If I only check for _operation it results in being nil when called from applicationShouldTerminate. Would be interesting to know why this is the case.
In any case, canceling now works properly: When the app is quit, it will cancel the data generation class and exits without crashing ... mostly anyway. But I still wonder why my NSOperation subclass' cancel() isn't called when I use processingQueue.cancelAllOperations()!
From Apple's documentation.
Canceling the operations does not automatically remove them from the queue or stop those that are currently executing. For operations that are queued and waiting execution, the queue must still attempt to execute the operation before recognizing that it is canceled and moving it to the finished state.
I would block the App's mainthread until the NSOperationQueue finishes all of its work.
I would call [NSOperationQueue cancelAllOperations] first.
Then in the 'Application will terminate' method I will call
[NSOperationQueue waitUntilAllOperationsAreFinished]. This will make sure the currently executing block(all the other queued tasks will be cancelled) will complete before the application quits.
Now if you are not comfortable with the main thread blocking until the currently executing block finishes, then you need to check for a flag(or an NSApplicationDelegate could be set on that class) which signals if the application is still active in order to continue. If application is to be terminated, then fall out of the block voluntarily, this is the cleanest way to do it.
Something roughly like the below.
void ^(aBlock)(void) = ^void(void)
{
for(NSUInteger i = 0;i < 1000; i++)
{
// heavy processing code. Taking several iterations each second
// At the start of each iteration, check for a flag, to see if to quit
if(_applicationIsShuttingDown) break;
// perform block operation
}
};
and your class is an NSApplicationDelegate and implements
-applicationWillTerminate:(NSNotification *)aNotification
{
_applicationIsShuttingDown = YES;
}
Using inheritance.
I have a child class that calls a method in the parent class that runs calls the server API.
-(IBAction)buttonPressed
{
[self methodInParentClassThatCallsTheAPI:param];
// This is where I would like the call back
if (success from the server API) // do something with the UI
else if (failure from the server API) // do so something else with the UI
}
Parent Class:
- (void)methodInParentClassThatCallsTheAPI:(NSString *)param
{
//The method below calls the server API and waits for a response.
[someServerOperation setCompletionBlockWithSuccess:^(param, param){
// Return a success flag to the Child class that called this method
} failure:^(param, NSError *error){
// Return a failure flag to the Child class that called this method
}
}
How can I accomplish this with a block? Is there a better way to do this other than the block? Code example please
Create a completion block on methodInParentClass like this:
- (void)methodInParentClassThatCallsTheAPI:(NSString *)param completionBlock:(void (^)(BOOL success))completionBlock;
Then fire it inside the block success/failure with the appropriate value like this:
completionBlock(YES);
EDIT: By the way, please note that the return may not be on the main thread so if you plan to do a UI update you can fire the return block with a dispatch_async(dispatch_get_main_queue, ^{});
EDIT2: Since you seem to suggest this is the result of a button tap if your child is a VC waiting on the return just remember that this block will return async (obviously since that's what it is designed for) so if you need to hold the user for whatever reason you'll need to have the main thread display a loading indicator of some sort and hold user input events. Just remember that if you don't the UI will continue responding when before the completion block fires so at the very least you will want to disable the button so the user can't multi-press it and then you can reenable it when the completion block fires from the child VC.
EDIT3: Ok here is the calling code.
-(IBAction)buttonPressed
{
[self methodInParentClassThatCallsTheAPI:param withCompletionBlock:^(BOOL success){
if (success) {
// do something with the UI
} else {
// Do something else
}
}];
}
The app I'm working on lets users manage some assets. The user can create / delete / edit / split / move assets around on the screen. Users need to be able to undo all these steps back.
The assets are managed with core data (and yes, the undoManager is instantiated).
For each of these actions I create undo groupings with this pair:
beginUndoGrouping ... endUndoGrouping
Here's a simple example (sequence 1):
// SPLIT
- (void) menuSplitPiece: (id) sender
{
[self.managedObjectContext.undoManager beginUndoGrouping];
[self.managedObjectContext.undoManager setActionName:#"Split"];
//... do the split
[self.managedObjectContext.undoManager endUndoGrouping];
// if the user cancels the split action, call [self.managedObjectContext.undoManager undo] here;
}
I do the same for edit: if the user cancels the edit, then I call undo right after endUndoGrouping.
Everything works beautiful with one exception: besides the groups that I create, there are other groups being created by Core Data which I cannot control. Here's what I mean by that:
I registered to receive NSUndoManagerDidCloseUndoGroupNotification notifications like so:
- (void) registerUndoListener
{
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(didCloseUndoGroup:)
name:NSUndoManagerDidCloseUndoGroupNotification object:nil];
...}
I use these notifications to refresh the Undo button and display the name of the action that is being undone as a result of some action: e.g. Undo Split
Yet, didCloseUndoGroup is called / notified twice for every action above (e.g. section 1, after endUndoGrouping):
At the time of the first notification, self.managedObjectContext.undoManager.undoActionName contains the undo action name I've set, which I expected, while the second time undoActionName is an empty string.
As a workaround, I tried to simply undo operations that had an empty name (Assuming they were not mine and I did not need them), and see whether I was missing anything.
Now, didCloseUndoGroup looks like this
- (void) didCloseUndoGroup: (NSNotification *) notification
{
...
if ([self.managedObjectContext.undoManager.undoActionName isEqualToString:#""]){
[self.managedObjectContext.undoManager undo];
}
[self refreshUndoButton]; // this method displays the name of the undo action on the button
...
}
And magically it works, I can undo any command, any number of layers using "undo". But this is not the way it should work...
Several other things I tried before that:
[self.managedObjectContext processPendingChanges] before opening any grouping. It was still sending two notifications.
Another thing I tried was disableUndoRegistration / enableUndoRegistration. This one generated an exception: "invalid state, undo was called with too many nested undo groups"
None of the above helped me "isolate" the mysterious groupings I mentioned before.
I should not be receiving NSUndoManagerDidCloseUndoGroupNotification notifications twice. Or, should I? Is there a better way to deal with this situation?
UPDATE
This is what finally worked. Previously I was automatically undoing the no-name groups as soon as I received a notification. This is what caused the problem. Now, I undo everything until I reach my target group and then I do a last undo for that group.
"undoManagerHelper" is just a stack management system that generates a unique ID for each command that is pushed on the stack. I use this unique ID to name the group.
- (BOOL) undoLastAction
{
NSString *lastActionID = [self.undoManagerHelper pop]; // the command I'm looking for
if (lastActionID == nil) return false;
//... undo until there is nothing to undo or self.managedObjectContext.undoManager.undoActionName equals lastActionID
//the actual undo here
if ([currentActionID isEqualToString: lastActionID] && [self.managedObjectContext.undoManager canUndo]){
[self.managedObjectContext.undoManager undo];
}
return true;
}
- (void) beginUndoGroupingWithName: (NSString *) name
{
[self.managedObjectContext processPendingChanges];
[self.managedObjectContext.undoManager beginUndoGrouping];
NSString *actionID = [self.undoManagerHelper push: name];
[self.managedObjectContext.undoManager setActionName:actionID];
}
- (void) closeLastUndoGrouping
{
[self.managedObjectContext.undoManager endUndoGrouping];
[self.managedObjectContext processPendingChanges];
}
According to the documentation for beginUndoGrouping - https://developer.apple.com/library/ios/#documentation/Cocoa/Reference/Foundation/Classes/NSUndoManager_Class/Reference/Reference.html - "By default undo groups are begun automatically at the start of the event loop, but you can begin your own undo groups with this method, and nest them within other groups." The unnamed group is the default undo group that contains all operations, it sounds like you should ignore the unnamed group for your situation.
I have an Cocoa Application (Mac OS X SDK 10.7) that is performing some processes via Grand Central Dispatch (GCD). These processes are manipulating some Core Data NSManagedObjects (non-document-based) in a manner that I believe is thread safe (creating a new managedObjectContext for use in this thread).
The problem I have is when the user tries to quit the application while the dispatch queue is still running.
The NSApplication delegate is being called before actually quitting.
- (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication *)sender
I get an error "Could not merge changes." Which is somewhat expected since there are still operations being performed through the different managedObjectContext. I am then presented with the NSAlert from the template that is generated with a core data application.
In the Threading Programming Guide there is a section called "Be Aware of Thread Behaviors at Quit Time" which alludes to using replyToApplicationShouldTerminate: method. I'm having a little trouble implementing this.
What I would like is for my application to complete processing the queued items and then terminate without presenting an error message to the user. It would also be helpful to update the view or use a sheet to let the user know that the app is performing some action and will terminate when the action is complete.
Where and how would I implement this behavior?
Solution:
So I had a few different issues here.
I had blocks that were accessing core data in a dispatch_queue preventing my application from terminating gracefully.
When I tried to add a new item to the dispatch_queue a new instance of the dispatch_queue was started on a new thread.
What I did to solve this was use NSNotificationCenter in my AppDelegate (where (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication *)sender was being called. In the template code 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 AppController add an observer for the 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 have also created a struct that can be checked against to see if the user wants to terminate the application. (I set the initial state of the struct in awakeFromNib above). Place the struct after your #synthesize statements:
struct {
bool isAppTerminating;
bool isTerminatingNow;
} appTerminating;
Now for the long-running dispatch_queue that is preventing the app from gracefully terminating. When I initially create this dispatch_queue, a for loop is used to add the items that need updating. After this for loop is executed, I have tacked on another queue item that will check the struct to see if the app should terminate:
// 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 to be called when the 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);
}
}
I haven't dealt with this myself, but just from my reading of the docs, it looks like what you should do is:
Return NSTerminateLater from applicationShouldTerminate:. This lets the system know that your app isn't ready to terminate just yet, but will do so shortly.
Enqueue a "final" block on your dispatch queue. (You need to make sure that other blocks are not enqueued after this. This block will then be run after all the other work has been performed. Note the queue must be serial -- not one of the concurrent queues) for this to work correctly.) The "final" block should do [NSApp replyToApplicationShouldTerminate:YES];, which will complete the normal termination process.
There isn't any direct way to find out whether a GCD queue is still working. The only other thing that you can do (that I know of) to handle this is to put all of the blocks into a dispatch group, and then wait on the group in applicationShouldTerminate: (using dispatch_group_wait().
In my application, I let a progress indicator starts animation before I send a HTTP request.
The completion handler is defined in a block. After I get the response data, I hide the progress indicator from inside the block. My question is, as I know, UI updates must be performed in the main thread. How can I make sure it?
If I define a method in the window controller which updates UI, and let the block calls the method instead of updating UI directly, is it a solution?
Also, if your app targets iOS >= 4 you can use Grand Central Dispatch:
dispatch_async(dispatch_get_main_queue(), ^{
// This block will be executed asynchronously on the main thread.
});
This is useful when your custom logic cannot easily be expressed with the single selector and object arguments that the performSelect… methods take.
To execute a block synchronously, use dispatch_sync() – but make sure you’re not currently executing on the main queue or GCD will deadlock.
__block NSInteger alertResult; // The __block modifier makes alertResult writable
// from a referencing block.
void (^ getResponse)() = ^{
NSAlert *alert = …;
alertResult = [NSAlert runModal];
};
if ([NSThread isMainThread]) {
// We're currently executing on the main thread.
// We can execute the block directly.
getResponse();
} else {
dispatch_sync(dispatch_get_main_queue(), getResponse);
}
// Check the user response.
if (alertResult == …) {
…
}
You probably misunderstood something. Using blocks doesn't mean that your code is running in a background thread. There are many plugins that work asynchronously (in another thread) and use blocks.
There are a few options to solve your problem.
You can check if your code is running in the main thread my using [NSThread isMainThread]. That helps you to make sure that you're not in the background.
You can also perform actions in the main or background by using performSelectorInMainThread:SEL or performSelectorInBackground:SEL.
The app immediately crashes when you're trying to call the UI from a bakcground thread so it's quite easy to find a bug.