iOS Unit Testing: Wait for Time Interval with Expectations - ios

I'm trying to update my asynchronous unit tests to use the new XCTestExpectation interface instead of manually spinning the run loop.
My unit tests previously utilized the functions waitForBlock, finishBlock, and waitForTimeInterval: which is simply a convenience method that called finishBlock after the specified time. I'm trying to update this setup to use expectations.
The tests that were utilizing waitForBlock + finishBlock semantics are all working just as expected after being replaced with waitForExpectationsWithTime:handler: and fulfill, but my solution to replace waitForTimeInterval: doesn't seem to be working.
- (void)waitForTimeInterval:(NSTimeInterval)delay
{
XCTestExpectation *expectation = [self expectationWithDescription:#"wait"];
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delay * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[expectation fulfill];
});
[self waitForExpectationsWithTimeout:delay + 1 handler:nil];
}
Edit:
Seems like that code actually does work... so this was probably just Xcode 6 screwing with me this afternoon.
I feel like it should be fairly straight-forward: Create an expectation, set up an asynchronous block that fulfills is, and wait. However, the dispatch_after block is never invoked.
My hunch is that waitForExpectationsWithTimeout:handler: blocks its current thread, which is the main queue, so the run loop never gets around to its asynchronous blocks. That seems reasonable, but I'm having trouble coming up with a different way to implement this functionality.
I'm looking for either 1) additional information about XCTestExpectation that might reveal a workaround, or 2) a different idea for implementing this functionality.

dispatch_async(dispatch_get_main_queue(), ...) doesn't seem to work in Xcode 7 UI Tests, the completion handler is never called. performSelector is no longer available in Swift, but there are two other workarounds:
Using a timer
var waitExpectation: XCTestExpectation?
func wait(duration: NSTimeInterval) {
waitExpectation = expectationWithDescription("wait")
NSTimer.scheduledTimerWithTimeInterval(duration, target: self,
selector: Selector("onTimer"), userInfo: nil, repeats: false)
waitForExpectationsWithTimeout(duration + 3, handler: nil)
}
func onTimer() {
waitExpectation?.fulfill()
}
Run the block on global queue (it works, but probably unsafe as it's not documented anywhere that XCTestExpectation is thread-safe).
func wait(duration: NSTimeInterval) {
let expectation = expectationWithDescription("wait")
let dispatchTime = dispatch_time(DISPATCH_TIME_NOW,
Int64(duration * Double(NSEC_PER_SEC)))
dispatch_after(dispatchTime,
dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)) {
expectation.fulfill()
}
waitForExpectationsWithTimeout(duration + 3, handler: nil)
}

I've used XCTestExpectation in a number of projects for all sorts of async web and GCD stuff... AFAIK, something like the following is what seems to be the most common usage:
- (void)testExample {
// create the expectation with a nice descriptive message
XCTestExpectation *expectation = [self expectationWithDescription:#"The request should successfully complete within the specific timeframe."];
// do something asyncronously
[someObject doAsyncWithCompletionHandler:^(NSInteger yourReturnValue) {
// now that the async task it done, test whatever states/values you expect to be after this is
// completed
NSInteger expectedValue = 42;
XCTAssert(yourReturnValue == expectedValue, #"The returned value doesn't match the expected value.");
// fulfill the expectation so the tests can complete
[expectation fulfill];
}];
// wait for the expectations to be called and timeout after some
// predefined max time limit, failing the test automatically
NSTimeInterval somePredefinedTimeout = 3;
[self waitForExpectationsWithTimeout:somePredefinedTimeout handler:nil];
}

Apparently using performSelector:withObject:afterDelay: works as expected, though I'm still not sure why. If anyone has an idea as to why GCD's dispatch_after doesn't work, please provide an additional answer and I will accept it. For now, this setup seems to work as expected:
- (void)waitForTimeInterval:(NSTimeInterval)delay
{
XCTestExpectation *expectation = [self expectationWithDescription:#"wait"];
[self performSelector:#selector(fulfillExpectation:) withObject:expectation afterDelay:delay];
[self waitForExpectationsWithTimeout:delay + 1 handler:nil];
}
- (void)fulfillExpectation:(XCTestExpectation *)expectation
{
[expectation fulfill];
}

In one of my Unit test cases I needed to test the running of a method in my main app code, which should trigger a timer in about 1 second to call another method in the app. I used XCTestExpectation wait and DispatchQueue.asyncAfter as a mechanism to stop and wait before I check the result. The following code is a snippet in Swift 3 / 4:
<call the main app method which will trigger a timer event>
// wait
let expectation = XCTestExpectation(description: "test")
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 2) {
expectation.fulfill()
}
wait(for: [expectation], timeout: 2.5)
<check the result of the timer event>

Related

Is it normal that CPU usage exceeds 100% using dispatch async in Xcode 7

I'm a beginner in swift 2, and I'm trying to make my program blocks while showing only a progress spinner until some operation finishes, I made that code snippet in a button with the action "touch up inside", my problem is that while debugging,Xcode 7 CPU usage jumps to 190 % once I tap my button and keeps high until the flag changes its value, Is it normal that CPU usage jumps like that?, also Is it a good practice to use the following snippet or shud i use sleep or some other mechanism inside my infinite loop?
let queue2 = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)
dispatch_async(self.queue2) { () -> Void in
while(flag == true)
{
//wait until flag sets to false from previous func
}
self.dispatch_main({
//continue after the flag became false
})
This is a very economical completion handler
func test(completion:() -> ())
{
// do hard work
completion()
}
let queue2 = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)
dispatch_async(queue2) {
test() {
print("completed")
}
}
or with additional dispatch to the main queue to update the UI
let queue2 = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)
dispatch_async(queue2) {
test() {
print("completed")
dispatch_async(dispatch_get_main_queue()) {
// update UI
}
}
}
This is totally wrong approach as you are using while loop for waiting. You should use Completion Handler to achieve this kind of stuff.
Completion handlers are callbacks that allow a client to perform some action when a framework method or function completes its task. Often the client uses a completion handler to free state or update the user interface. Several framework methods let you implement completion handlers as blocks (instead of, say, delegation methods or notification handlers).
Refer Apple documentation for more details.
I suppose you have a sort of class which manages these "some operation finishes".
When you finish your operations you can comunicate by completion handler or delegation. In the meanwhile you can disable the user interaction of your UI until the end of these operations.
If you provide more informations about your background operations I can add some snippets of code.

Performing an unknown amount of animations sequentially

I am creating a game where the user can move a SKShapeNode around. Now, I am trying to write a utility function that will perform back-to-back animations sequentially.
Description of what I'm Trying
I first dispatch_async to a serial thread. This thread then calls a dispatch_sync on the main thread to perform an animation. Now, to make the animations run sequentially, I would like to block the GlobalSerialAnimationQueue until the animation is completed on the main thread. By doing this, I (theoretically) would be able to run animations sequentially. My code is pasted below for more description
func moveToCurrentPosition() {
let action = SKAction.moveTo(self.getPositionForCurrRowCol(), duration: 1.0)
dispatch_async(GlobalSerialAnimateQueue) {
//this just creates an action to move to a point
dispatch_sync(GlobalMainQueue, {
self.userNode!.runAction(action) {
//inside the completion block now want to continue
//WOULD WANT TO TRIGGER THREAD TO CONTINUE HERE
}
})
//WOULD LIKE TO PAUSE HERE, THIS BLOCK FINISHING ONLY WHEN THE ANIMATION IS COMPLETE
}
}
So, my question is, how would I write a function that can take in requests for animations, and then perform them sequentially? What grand-central-dispatch tools should I use, or should I be trying a completely different approach?
I figured out how to do this using a grand-central-dispatch semaphore. My updated code is here.
func moveToCurrentPosition() {
let action = SKAction.moveTo(self.getPositionForCurrRowCol(), duration: Animation.USER_MOVE_DURATION)
dispatch_async(GlobalSerialAnimateQueue) {
let semaphore = dispatch_semaphore_create(0)
dispatch_sync(GlobalMainQueue, {
self.userNode!.runAction(action) {
//signal done
dispatch_semaphore_signal(semaphore)
}
})
//wait here...
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER)
}
}
SpriteKit provides a much simpler and intuitive API for running actions in sequence. Have a look at the documentation here.
You can simply perform actions as a sequence of events, with blocks in between or as completion:
let action = SKAction.moveTo(self.getPositionForCurrRowCol(), duration: Animation.USER_MOVE_DURATION)
let otherAction = SKAction.runBlock({
//Perform completion here.
})
self.userNode!.runAction(SKAction.sequence([action, otherAction]))

Timing issue with dispatch_source_t handler function - am I following the right pattern for dispatch timer?

I read the documentation and came to know that timer (dispatch_source_t) skips to fire if the handler is still in progress for previous iterations.
But this whole business of handler taking it longer makes this inaccurate. And I am observing that I am unable to stop the timer at intended times.
My code looks like this:
double secondsToFire = 1.0f;
dispatch_queue_t queue = dispatch_get_main_queue();
m_myTimer = CreateDispatchTimer(secondsToFire, queue,
^{
//Do some time consuming operation, taking any number of seconds
int retVal = DoSomeOperation();
if (retVal == 1)
{
cancelTimer(m_myTimer);
SomeOtherOperation();
}
});
dispatch_source_t CreateDispatchTimer(double interval, dispatch_queue_t queue, dispatch_block_t block)
{
dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
if (timer)
{
// dispatch_source_set_timer(timer, dispatch_time(DISPATCH_TIME_NOW, interval * NSEC_PER_SEC), interval * NSEC_PER_SEC, (1ull * NSEC_PER_SEC) / 10);
dispatch_source_set_timer(timer, dispatch_time(DISPATCH_TIME_NOW, 0), interval * NSEC_PER_SEC, 0);
dispatch_source_set_event_handler(timer, block);
dispatch_resume(timer);
}
return timer;
}
void cancelTimer(dispatch_source_t _timer)
{
if (_timer)
{
dispatch_source_cancel(_timer);
_timer = nil;
}
}
Note:
Inside DoSomeOperation(), I have code enclosed with #synchronized(array), in which I access an array who is being written by another private queue. But entire DoSomeOperation() is executed on main queue.
My question is, is this is the right and accurate timing model? I am posting here because I am facing lot of inaccuracies - timer doesn't fire every second, and it doesn't stop as intended too. I am able to observe that SomeOtherOperation() gets called when retVal == 1, but timer isn't done yet.
Another Note:
m_myTimer above is an iVar, and my project is ARC, if that could make any difference.
No, a dispatch timer doesn't get "skipped" if it fires while your handler is running, the handler will get re-invoked for the pending event right away once the previous invocation returns.
If multiple firings occur while the handler is running or enqueued (or while the source is suspended), they will get all get coalesced into a single handler invocation (as is the case for all edge-triggered source types).
You can check how many firings a given handler invocation is for with dispatch_source_get_data()
My concern was about accuracy in starting, firing and stopping, and not in correct reporting of things.
I finally ended up assigning one of my tasks off a private queue instead of main one. The advantage could be clearly visible in timer accuracy and prompt timer cancellation.
Conclusion:
More the same (esp. main) queue getting flogged by timer tasks, more they become inaccurate.

Waiting for multiple blocks to finish

I have those methods to retrieve some object information from the internet:
- (void)downloadAppInfo:(void(^)())success
failure:(void(^)(NSError *error))failure;
- (void)getAvailableHosts:(void(^)())success
failure:(void(^)(NSError *error))failure;
- (void)getAvailableServices:(void(^)())success
failure:(void(^)(NSError *error))failure;
- (void)getAvailableActions:(void(^)())success
failure:(void(^)(NSError *error))failure;
The downloaded stuff gets stored in object properties, so that is why the success functions return nothing.
Now, I want to have one method like this:
- (void)syncEverything:(void(^)())success
failure:(void(^)(NSError *error))failure;
Which does nothing else than calling all the methods above, and returning only after every single method has performed its success or failure block.
How can I do this?
Hint: I am aware that cascading the methods calls in each others success block would work. But this is neither 'clean' nor helpful when later implementations include further methods.
Attempts:
I tried running each of the calls in an NSOperation and adding those NSOperations to an NSOperationQueue followed by a "completion operation" which depends on every one of the preceding operations.
This won't work. Since the operations are considered completed even before their respective success/failure blocks return.
I also tried using dispatch_group. But it is not clear to me wether I am doing it the right way. Unfortunately, it is not working.
Drawn from the comments in other answers here, and the blog post Using dispatch groups to wait for multiple web services, I arrived at the following answer.
This solution uses dispatch_group_enter and dispatch_group_leave to determine when each intermediate task is running. When all tasks have finished, the final dispatch_group_notify block is called. You can then call your completion block, knowing that all intermediate tasks have finished.
dispatch_group_t group = dispatch_group_create();
dispatch_group_enter(group);
[self yourBlockTaskWithCompletion:^(NSString *blockString) {
// ...
dispatch_group_leave(group);
}];
dispatch_group_enter(group);
[self yourBlockTaskWithCompletion:^(NSString *blockString) {
// ...
dispatch_group_leave(group);
}];
dispatch_group_notify(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0),^{
// All group blocks have now completed
if (completion) {
completion();
}
});
Grand Central Dispatch - Dispatch Groups
https://developer.apple.com/documentation/dispatch/dispatchgroup
Grouping blocks allows for aggregate synchronization. Your application can submit multiple blocks and track when they all complete, even though they might run on different queues. This behavior can be helpful when progress can’t be made until all of the specified tasks are complete.
Xcode Snippet:
I find myself using Dispatch Groups enough that I've added the following code as an Xcode Snippet for easy insertion into my code.
Now I type DISPATCH_SET and the following code is inserted. You then copy and paste an enter/leave for each of your async blocks.
Objective-C:
dispatch_group_t group = dispatch_group_create();
dispatch_group_enter(group);
dispatch_group_leave(group);
dispatch_group_notify(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0),^{
});
Swift:
let dispatchGroup = DispatchGroup()
dispatchGroup.enter()
dispatchGroup.leave()
dispatchGroup.notify(queue: .global()) {
}
You were almost there, the problem is most likely to be that those methods are asynchronous, so you need an extra synchronization step. Just try with the following fix:
for(Appliance *appliance in _mutAppliances) {
dispatch_group_async(
group,
dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
dispatch_semaphore_t sem = dispatch_semaphore_create( 0 );
NSLog(#"Block START");
[appliance downloadAppInfo:^{
NSLog(#"Block SUCCESS");
dispatch_semaphore_signal(sem);
}
failure:^(NSError *error){
NSLog(#"Block FAILURE");
dispatch_semaphore_signal(sem);
}];
dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
NSLog(#"Block END");
});
dispatch_group_notify(
group,
dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0),^{
NSLog(#"FINAL block");
success();
});
}
One other solution is to use a Promise which is available in a few third party libraries.
I'm the author of RXPromise, which implements the Promises/A+ specification.
But there are at least two other Objective-C implementations.
A Promise represents the eventual result of an asynchronous method or operation:
-(Promise*) doSomethingAsync;
The promise is a complete replacement for the completion handler. Additionally, due to its clear specification and underlaying design, it has some very useful features which make it especially easy to handle rather complex asynchronous problems.
What you need to do first, is to wrap your asynchronous methods with completion handlers into asynchronous methods returning a Promise:
(Purposefully, your methods return the eventual result and a potential error in a more convenient completion handler)
For example:
- (RXPromise*) downloadAppInfo {
RXPromise* promise = [RXPromise new];
[self downloadAppInfoWithCompletion:^(id result, NSError *error) {
if (error) {
[promise rejectWithReason:error];
}
else {
[promise fulfillWithValue:result];
}
}];
return promise;
}
Here, the original asynchronous method becomes the "resolver" of the promise. A promise can be either fulfilled (success) or rejected (failure) with either specifying the eventual result of the task or the reason of the failure. The promise will then hold the eventual result of the asynchronous operation or method.
Note that the wrapper is an asynchronous method, which returns immediately a promise in a "pending" state.
Finally, you obtain the eventual result by "registering" a success and a failure handler with a then method or property. The few promise libraries around do differ slightly, but basically it may look as follows:
`promise.then( <success-handler>, <error-handler> )`
The Promise/A+ Specification has a minimalistic API. And the above is basically ALL one need for implementing the Promise/A+ spec - and is often sufficient in many simple use cases.
However, sometimes you need bit more - for example the OPs problem, which require to "wait" on a set of asynchronous methods and then do something when all have completed.
Fortunately, the Promise is an ideal basic building block to construct more sophisticated helper methods quite easily.
Many Promise libraries provide utility methods. So for example a method all (or similar) which is an asynchronous method returning a Promise and taking an array of promises as input. The returned promise will be resolved when all operations have been completed, or when one fails. It may look as follows:
First construct an array of promises, and simultaneously starting all asynchronous tasks in parallel:
NSArray* tasks = #[
[self downloadAppInfo],
[self getAvailableHosts],
[self getAvailableServices],
[self getAvailableActions],
];
Note: here, the tasks are already running (and may complete)!
Now, use a helper method which does exactly what stated above:
RXPromise* finalPromise = [RXPromise all:tasks];
Obtain the final results:
finalPromise.then(^id( results){
[self doSomethingWithAppInfo:results[0]
availableHosts:results[1]
availableServices:results[2]
availableActions:results[3]];
return nil;
}, ^id(NSError* error) {
NSLog(#"Error %#", error); // some async task failed - log the error
});
Note that either the success or the failure handler will be called when the returned promise will be resolved somehow in the all: method.
The returned promise (finalPromise) will be resolved, when
all tasks succeeded successfully, or when
one task failed
For case 1) the final promise will be resolved with an array which contains the result for each corresponding asynchronous task.
In case 2) the final promise will be resolved with the error of the failing asynchronous task.
(Note: the few available libraries may differ here)
The RXPromise library has some additional features:
Sophisticated cancellation which forwards a cancellation signal in the acyclic graph of promises.
A way to specify a dispatch queue where the handler will run. The queue can be used to synchronize access to shared resources for example, e.g.
self.usersPromise = [self fetchUsers];
self.usersPromise.thenOn(dispatch_get_main_queue(), ^id(id users) {
self.users = users;
[self.tableView reloadData];
}, nil);
When compared to other approaches, the dispatch_group solution suffers from the fact that it blocks a thread. This is not quite "asynchronous". It's also quite complex if not impossible to implement cancellation.
The NSOperation solution appears to be a mixed blessing. It may be elegant only if you already have NSOperations, and if you have no completion handlers which you need to take into account when defining the dependencies - otherwise, it becomes cluttered and elaborated.
Another solution, not mentioned so far, is Reactive Cocoa. IMHO, it's an awesome library which lets you solve asynchronous problems of virtually any complexity. However, it has a quite steep learning curve, and may add a lot of code to your app. And I guess, 90% of asynchronous problems you stumble over can be solved with cancelable promises. If you have even more complex problems, so take a look at RAC.
If you want to create a block based solution you could do something like
- (void)syncEverything:(void(^)())success failure:(void(^)(NSError *error))failure
{
__block int numBlocks = 4;
__block BOOL alreadyFailed = NO;
void (^subSuccess)(void) = ^(){
numBlocks-=1;
if ( numBlocks==0 ) {
success();
}
};
void (^subFailure)(NSError*) = ^(NSError* error){
if ( !alreadyFailed ) {
alreadyFailed = YES;
failure(error);
}
};
[self downloadAppInfo:subSuccess failure:subFailure];
[self getAvailableHosts:subSuccess failure:subFailure];
[self getAvailableServices:subSuccess failure:subFailure];
[self getAvailableActions:subSuccess failure:subFailure];
}
It's kind of quick and dirty, and you might need to do block copys. If more than one method fails, you will only get one overall failure.
Here is my solution without any dispatch_group.
+(void)doStuffWithCompletion:(void (^)(void))completion{
__block NSInteger stuffRemaining = 3;
void (^dataCompletionBlock)(void) = ^void(void) {
stuffRemaining--;
if (!stuffRemaining) {
completion();
}
};
for (NSInteger i = stuffRemaining-1; i > 0; i--) {
[self doOtherStuffWithParams:nil completion:^() {
dataCompletionBlock();
}];
}
}

Serializing asynchronous tasks in objective C

I wanted to be able to serialize 'genuinely' async methods, for example:
making a web request
showing a UIAlertView
This is typically a tricky business and most samples of serial queues show a 'sleep' in an NSBlockOperation's block. This doesn't work, because the operation is only complete when the callback happens.
I've had a go at implementing this by subclassing NSOperation, here's the most interesting bits of the implementation:
+ (MYOperation *)operationWithBlock:(CompleteBlock)block
{
MYOperation *operation = [[MYOperation alloc] init];
operation.block = block;
return operation;
}
- (void)start
{
[self willChangeValueForKey:#"isExecuting"];
self.executing = YES;
[self didChangeValueForKey:#"isExecuting"];
if (self.block) {
self.block(self);
}
}
- (void)finish
{
[self willChangeValueForKey:#"isExecuting"];
[self willChangeValueForKey:#"isFinished"];
self.executing = NO;
self.finished = YES;
[self didChangeValueForKey:#"isExecuting"];
[self didChangeValueForKey:#"isFinished"];
}
- (BOOL)isFinished
{
return self.finished;
}
- (BOOL) isExecuting
{
return self.executing;
}
This works well, here's a demonstration...
NSOperationQueue *q = [[NSOperationQueue alloc] init];
q.maxConcurrentOperationCount = 1;
dispatch_queue_t queue = dispatch_queue_create("1", NULL);
dispatch_queue_t queue2 = dispatch_queue_create("2", NULL);
MYOperation *op = [MYOperation operationWithBlock:^(MYOperation *o) {
NSLog(#"1...");
dispatch_async(queue, ^{
[NSThread sleepForTimeInterval:2];
NSLog(#"1");
[o finish]; // this signals we're done
});
}];
MYOperation *op2 = [MYOperation operationWithBlock:^(MYOperation *o) {
NSLog(#"2...");
dispatch_async(queue2, ^{
[NSThread sleepForTimeInterval:2];
NSLog(#"2");
[o finish]; // this signals we're done
});
}];
[q addOperations:#[op, op2] waitUntilFinished:YES];
[NSThread sleepForTimeInterval:5];
Note, I also used a sleep but made sure these were executing in background thread to simulate a network call. The log reads as follows
1...
1
2...
2
Which is as desired. What is wrong with this approach? Are there any caveats I should be aware of?
"Serializing" asynchronous tasks will be named actually "continuation" (see also this wiki article Continuation.
Suppose, your tasks can be defined as an asynchronous function/method with a completion handler whose parameter is the eventual result of the asynchronous task, e.g.:
typedef void(^completion_handler_t)(id result);
-(void) webRequestWithCompletion:(completion_handler_t)completionHandler;
-(void) showAlertViewWithResult:(id)result completion:(completion_handler_t)completionHandler;
Having blocks available, a "continuation" can be easily accomplished through invoking the next asynchronous task from within the previous task's completion block:
- (void) foo
{
[self webRequestWithCompletion:^(id result) {
[self showAlertViewWithResult:result completion:^(id userAnswer) {
NSLog(#"User answered with: %#", userAnswer);
}
}
}
Note that method foo gets "infected by "asynchrony" ;)
That is, here the eventual effect of the method foo, namely printing the user's answer to the console, is in fact again asynchronous.
However, "chaining" multiple asynchronous tasks, that is, "continuing" multiple asynchronous tasks, may become quickly unwieldy:
Implementing "continuation" with completion blocks will increment the indentation for each task's completion handler. Furthermore, implementing a means to let the user cancel the tasks at any state, and also implement code to handle the error conditions, the code gets quickly confusing, difficult to write and difficult to understand.
A better approach to implement "continuation", as well as cancellation and error handling, is using a concept of Futures or Promises. A Future or Promise represents the eventual result of the asynchronous task. Basically, this is just a different approach to "signal the eventual result" to the call site.
In Objective-C a "Promise" can be implemented as an ordinary class. There are third party libraries which implement a "Promise". The following code is using a particular implementation, RXPromise.
When utilizing such a Promise, you would define your tasks as follows:
-(Promise*) webRequestWithCompletion;
-(Promise*) showAlertViewWithResult:(id)result;
Note: there is no completion handler.
With a Promise, the "result" of the asynchronous task will be obtained via a "success" or an "error" handler which will be "registered" with a then property of the promise. Either the success or the error handler gets called by the task when it completes: when it finishes successfully, the success handler will be called passing its result to the parameter result of the success handler. Otherwise, when the task fails, it passes the reason to the error handler - usually an NSError object.
The basic usage of a Promise is as follows:
Promise* promise = [self asyncTasks];
// register handler blocks with "then":
Promise* handlerPromise = promise.then( <success handler block>, <error handler block> );
The success handler block has a parameter result of type id. The error handler block has a parameter of type NSError.
Note that the statement promise.then(...) returns itself a promise which represents the result of either handler, which get called when the "parent" promise has been resolved with either success or error. A handler's return value may be either an "immediate result" (some object) or an "eventual result" - represented as a Promise object.
A commented sample of the OP's problem is shown in the following code snippet (including sophisticated error handling):
- (void) foo
{
[self webRequestWithCompletion] // returns a "Promise" object which has a property "then"
// when the task finished, then:
.then(^id(id result) {
// on succeess:
// param "result" is the result of method "webRequestWithCompletion"
return [self showAlertViewWithResult:result]; // note: returns a promise
}, nil /*error handler not defined, fall through to the next defined error handler */ )
// when either of the previous handler finished, then:
.then(^id(id userAnswer) {
NSLog(#"User answered with: %#", userAnswer);
return nil; // handler's result not used, thus nil.
}, nil)
// when either of the previous handler finished, then:
.then(nil /*success handler not defined*/,
^id(NEError* error) {
// on error
// Error handler. Last error handler catches all errors.
// That is, either a web request error or perhaps the user cancelled (which results in rejecting the promise with a "User Cancelled" error)
return nil; // result of this error handler not used anywhere.
});
}
The code certainly requires more explanation. For a detailed and a more comprehensive description, and how one can accomplish cancellation at any point in time, you may take a look at the RXPromise library - an Objective-C class which implements a "Promise". Disclosure: I'm the author of RXPromise library.
At a first glance this would work, some parts are missing to have a "proper" NSOperation subclass though.
You do not cope with the 'cancelled' state, you should check isCancelled in start, and not start if this returns YES ("responding to the cancel command")
And the isConcurrent method needs to be overridden too, but maybe you omitted that for brevity.
When subclassing NSOperation I would strongly suggest only overriding main unless you really know what you are doing as it is really easy to mess up thread safety. While the documentation says that the operation will not be concurrent the act of running them through an NSOperationQueue automatically makes them concurrent by running them on a separate thread. The non-concurrency note only applies if you call the start method of the NSOperation yourself. You can verify this by noting the thread ID that each NSLog line contains. For example:
2013-09-17 22:49:07.779 AppNameGoesHere[58156:ThreadIDGoesHere] Your log message goes here.
The benefit of overriding main means that you don't have to deal with thread safety when changing the state of the operation NSOperation handles all of that for you. The main thing that is serializing your code is the line that sets maxConcurrentOperationCount to 1. This means each operation in the queue will wait for the next to run (all of them will run on a random thread as determined by the NSOperationQueue). The act of calling dispatch_async inside each operation also triggers yet another thread.
If you are dead set on using subclassing NSOperation then only override main, otherwise I would suggest using NSBlockOperation which seems like what you are somewhat replicating here. Really though I would avoid NSOperation altogether, the API is starting to show its age and is very easy to get wrong. As an alternative I would suggest something like RXPromise or my own attempt at solving this problem, FranticApparatus.

Resources