3

In my iOS app, things are mostly driven by events.

  1. The user presses a button
  2. The app listens for an external device to be connected to the iOS device
  3. Once the device is detected (is connected), an asynchronous call is made to a web service (to check for user authenticity)
  4. The web service returns the user's authenticity (such as a boolean)
  5. Another asynchronous web service is called for a stage 2 authenticity
  6. Result is returned

My question is: If I give the user a dialog box saying "Please wait, processing" and I give them a "Cancel" button, how should I (or how might I) abort everything that's happening?

One way to do this is to simply have a boolean called "userDidCancelEverything" and every event-driven method checks to see if this is true. This seems ugly though.

Rowan Freeman
  • 3,478
  • 4
  • 30
  • 41

2 Answers2

1

How you implement a cancel function will obviously depend on how you implement the events in your app. For example, when the user taps the button the resulting action might add a number of operations to a serial operation queue: the first operation would wait for the device to be connected, the next would call the authentication web service, and so on. If you do it that way, then your cancel button's action would call the operation queue's -cancelAllEvents method to empty the queue and then reset the state related to the operations to a known state. It'd be similar if you use some other scheme -- stop and remove any pending events and then set the app back to a known state.

Caleb
  • 38,959
  • 8
  • 94
  • 152
  • I haven't used operation queues since I'm quite new. They look interesting though so I'll take a look and see if it's relevant to what I'm doing. Thanks for the response. – Rowan Freeman Feb 28 '13 at 00:22
1

I known, this is an old question, but interestingly enough, and there is a nice solution:

Suppose, all those functions were asynchronous. The asynchronous pattern is called a "Continuation".

A possible implementation of such a "continuation" could use dispatch queues and blocks directly, for example:

dispatch(my_queue, ^{
    [self connectDeviceWithCompletion:^(id result){
        if (result) {
            [self authenticateWithCompletion:^(id result){
                 if (result) {
                     [self performTwoFactorAuthWithCompletion:^(id result) {
                         if (result) {
                              ...
                         }
                     }];
                 }
            }];
        }
    }];
});

As you can see, "continuation" with dispatch and blocks becomes quickly confusing, and I didn't even add error checking, and cancellation.

Note, that canceling a block which has been enqueued isn't that easily possible.

A more comprehensible solution that also supports error handling and cancellation can be achieved using a concept called "Promise" (see wiki Futures and promises]. Promises are very popular in JavaScript and other languages, but there are a few Objective-C libraries which implement Promises, see this answer: success:/failure: blocks vs completion: block.

Utilizing the RXPromise library one could write:

RXPromise* finalResult = [self connectDevice]
.then(^(id result){
    // device successfully connected with result
    return [self authenticate];
}, nil)
.then(^id(id result){
    // authenticated successfully with result
    return [self performTwoFactorAuth];
}, nil)
.then(^id(id result){
    // successfully performed two-factor-authentication
    // do something 
    ...
}, nil)

[finalResult setTimeout:5*60]; // we cancel everything after 5 minutes

// Catch any error:
finalResult.then(nil, id(NSError* error){
    NSLog(@"Something went wrong, or user cancelled with error: %@", error);
});

self.finalResultPromise = finalResult;
...

Later:

Suppose, the user waits for an result to happen eventually. When navigating back from the current view, this shall cancel all the asynchronous operations:

- (void) viewDidDisappear:(BOOL)animated {
    // we are no longer interested in the result         
    [self.finalResultPromise cancel];
    self.finalResultPromise = nil;

    [super viewDidDisappear:animated];
}

Disclosure: I'm the author of RXPromsie ;)

CouchDeveloper
  • 191
  • 1
  • 4