24

I see two common patterns for blocks in Objective-C. One is a pair of success:/failure: blocks, the other is a single completion: block.

For example, lets say I have a task that will return an object asynchronously and that task might fail. The first pattern is -taskWithSuccess:(void (^)(id object))success failure:(void (^)(NSError *error))failure. The second pattern is -taskWithCompletion:(void (^)(id object, NSError *error))completion.

success:/failure:

[target taskWithSuccess:^(id object) {
    // W00t! I've got my object
} failure:^(NSError *error) {
    // Oh noes! report the failure.
}];

completion:

[target taskWithCompletion:^(id object, NSError *error) {
    if (object) {
        // W00t! I've got my object
    } else {
        // Oh noes! report the failure.
    }
}];

Which is the preferred pattern? What are the strengths and weaknesses? When would you use one over the other?

11
  • I'm pretty sure Objective-C has exception handling with throw/catch, is there a reason you can't use that? Jan 23, 2013 at 18:29
  • Either of these permit chaining async calls, which exceptions don't give you. Jan 23, 2013 at 18:37
  • 5
    @FrustratedWithFormsDesigner: stackoverflow.com/a/3678556/2289 - idiomatic objc doesn't use try/catch for flow control.
    – Ant
    Jan 23, 2013 at 18:53
  • 1
    Please consider moving your Answer from the question to an answer... after all, it is an answer (and you can answer your own questions).
    – user40980
    Jan 30, 2013 at 20:23
  • 1
    I finally caved to peer pressure and moved my answer to an actual answer. Dec 1, 2013 at 11:27

5 Answers 5

8

Completion callback (opposed to success/failure pair) is more generic. If you need to prepare some context before dealing with return status, you can do it just before "if (object)" clause. In success/failure case you have to duplicate this code. This depends on callback semantics, of course.

6
  • Can't comment on original question... Exceptions are not valid flow control in objective-c (well, cocoa) and should not be used as such. Thrown exception should be catched only to terminate gracefully.
    – user79302
    Jan 23, 2013 at 18:55
  • Yeah, I can see that. If -task… could return the object, but the object is not in the correct state, then you would still need error handling in the success condition. Jan 23, 2013 at 19:00
  • Yeah, and if block is not in-place, but is passed as argument to your controller, you have to toss two blocks around. This may be boring when callback needs to be passed through many layers. You always can split/compose it back, though.
    – user79302
    Jan 23, 2013 at 19:05
  • I don't understand how completion handler is more generic. Completion basically turns multiple method params into one - in the form of block params. Also, does generic mean better? In MVC you often times have duplicate code in view controller as well, that's a necessary evil due to separation of concerns. I don't think that's a reason to stay away from MVC though.
    – Boon
    Oct 28, 2014 at 1:36
  • @Boon One reason I see the single handler as being more generic is for cases where you would prefer the callee/handler/block itself determine if an operation succeeded or failed. Consider partial success cases where you possibly have an object with partial data and your error object is an error indicating that not all data was returned. The block could examine the data itself and check to see if it is sufficient. This is not possible with the binary success/fail callback scenario.
    – Travis
    Jan 20, 2016 at 8:14
8

I would say, whether the API provides one completion handler or a pair of success/failure blocks, is primarily a matter of personal preference.

Both approaches have pros and cons, although there are only marginally differences.

Consider that there are also further variants, for example where the one completion handler may have only one parameter combining the eventual result or a potential error:

typedef void (^completion_t)(id result);

- (void) taskWithCompletion:(completion_t)completionHandler;

[self taskWithCompletion:^(id result){
    if ([result isKindOfError:[NSError class]) {
        NSLog(@"Error: %@", result);
    }
    else {
        ...
    }
}]; 

The purpose of this signature is that a completion handler can be used generically in other APIs.

For example in Category for NSArray there is a method forEachApplyTask:completion: which sequentially invokes a task for each object and breaks the loop IFF there was an error. Since this method is itself asynchronous as well, it has a completion handler as well:

typedef void (^completion_t)(id result);
typedef void (^task_t)(id input, completion_t);
- (void) forEachApplyTask:(task_t)task completion:(completion_t);

In fact, completion_t as defined above is generic enough and sufficient to handle all scenarios.

However, there are other means for an asynchronous task to signal its completion notification to the call-site:

Promises

Promises, also called “Futures”, “Deferred” or “Delayed” represent the eventual result of an asynchronous task (see also: wiki Futures and promises).

Initially, a promise is in the “pending” state. That is, it’s “value” is not yet evaluated and not yet available.

In Objective-C, a Promise would be an ordinary object which will be returned from an asynchronous method as shown below:

- (Promise*) doSomethingAsync;

! The initial state of a Promise is “pending”.

Meanwhile, the asynchronous tasks starts to evaluate its result.

Note also, that there is no completion handler. Instead, the Promise will provide a more powerful means where the call-site can obtain the eventual result of the asynchronous task, which we will see soon.

The asynchronous task, which created the promise object, MUST eventually “resolve” its promise. That means, since a task may either succeed or fail, it MUST either “fulfill” a promise passing it the evaluated result, or it MUST “reject” the promise passing it an error indicating the reason for the failure.

! A task must eventually resolve its promise.

When a Promise has been resolved, it cannot change it's state anymore, including its value.

! A Promise can be resolved only once.

Once a promise has been resolved, a call-site can obtain the result (whether it failed or succeeded). How this is accomplished depends on whether the promise is implemented using the synchronous or the asynchronous style.

A Promise can be implemented in a synchronous or an asynchronous style which leads to either blocking respectively non-blocking semantics.

In a synchronous style in order to retrieve the value of the promise, a call-site would use a method which will block the current thread until after the promise has been resolved by the asynchronous task and the eventual result is available.

In an asynchronous style, the call-site would register callbacks or handler blocks which get called immediately after the promise has been resolved.

It turned out that the synchronous style has a number of significant disadvantages which effectively defeat the merits of asynchronous tasks. An interesting article about the currently flawed implementation of “futures” in the standard C++11 lib can be read here: Broken promises–C++0x futures.

How, in Objective-C, would a call-site obtain the result?

Well, it’s probably best to show a few examples. There are a couple of libraries which implement a Promise (see links below).

However, for the next code snippets, I will use a particular implementation of a Promise library, available on GitHub RXPromise. I’m the author of RXPromise.

The other implementations may have a similar API, but there can be small and possibly subtle differences in syntax. RXPromise is an Objective-C version of the Promise/A+ specification which defines an open standard for robust and interoperable implementations of promises in JavaScript.

All promise libraries listed below do implement the asynchronous style.

There are quite significant differences among the different implementations. RXPromise internally utilizes dispatch lib, is fully thread safe, extremely lightweight, and also provides a number of additional useful features, like cancellation.

A call-site obtains the eventual result of the asynchronous task through “registering” handlers. The “Promise/A+ specification” defines the method then.

The method then

With RXPromise it looks as follows:

promise.then(successHandler, errorHandler);

where successHandler is a block which gets called when the promise has been “fulfilled” and errorHandler is a block which gets called when the promise has been “rejected”.

! then is used to obtain the eventual result and to define a success or an error handler.

In RXPromise, the handler blocks have the following signature:

typedef id (^success_handler_t)(id result);
typedef id (^error_handler_t)(NSError* error);

The success_handler has a parameter result which is obviously the eventual result of the asynchronous task. Likewise, the error_handler has a parameter error which is the error reported by the asynchronous task when it failed.

Both blocks have a return value. What this return value is about, will become clear soon.

In RXPromise, then is a property which returns a block. This block has two parameters, the success handler block and the error handler block. The handlers must be defined by the call-site.

! The handlers must be defined by the call-site.

So, the expression promise.then(success_handler, error_handler); is a short form of

then_block_t block promise.then;
block(success_handler, error_handler);

We can write even more concise code:

doSomethingAsync
.then(^id(id result){
    …
    return @“OK”;
}, nil);

The code reads: “Execute doSomethingAsync, when it succeeds, then execute success handler”.

Here, the error handler is nil which means, in case of an error, it will not be handled in this promise.

Another important fact is that calling the block returned from property then will return a Promise:

! then(...) returns a Promise

When calling the block returned from property then, the “receiver” returns a new Promise, a child promise. The receiver becomes the parent promise.

RXPromise* rootPromise = asyncA();
RXPromise* childPromise = rootPromise.then(successHandler, nil);
assert(childPromise.parent == rootPromise);

What does that mean?

Well, due to this we can “chain” asynchronous tasks which effectively get executed sequentially.

Furthermore, the return value of either handler will become the “value” of the returned promise. So, if the task succeeds with the eventual result @“OK”, the returned promise will be “resolved” (that is “fulfilled”) with value @“OK”:

RXPromise* returnedPromise = asyncA().then(^id(id result){
    return @"OK";
}, nil);

...
assert([[returnedPromise get] isEqualToString:@"OK"]);

Likewise, when the asynchronous task fails, the returned promise will be resolved (that is “rejected”) with an error.

RXPromise* returnedPromise = asyncA().then(nil, ^id(NSError* error){
    return error;
});

...
assert([[returnedPromise get] isKindOfClass:[NSError class]]);

The handler may also return another promise. For example when that handler executes another asynchronous task. With this mechanism we can “chain” asynchronous tasks:

RXPromise* returnedPromise = asyncA().then(^id(id result){
    return asyncB(result);
}, nil);

! The return value of a handler block becomes the value of the child promise.

If there is no child promise, the return value has no effect.

A more complex example:

Here, we execute asyncTaskA, asyncTaskB, asyncTaskC and asyncTaskD sequentially - and each subsequent task takes the result of the preceding task as input:

asyncTaskA()
.then(^id(id result){
    return asyncTaskB(result);
}, nil)
.then(^id(id result){
    return asyncTaskC(result);
}, nil)
.then(^id(id result){
    return asyncTaskD(result);
}, nil)
.then(^id(id result){
    // handle result
    return nil;
}, nil);

Such a “chain” is also called “continuation”.

Error handling

Promises make it especially easy to handle errors. Errors will be “forwarded” from the parent to the child if there is no error handler defined in the parent promise. The error will be forwarded up the chain until a child handles it. Thus, having the above chain, we can implement error handling just by adding another “continuation” which deals with a potential error which may happen anywhere above:

asyncTaskA()
.then(^id(id result){
    return asyncTaskB(result);
}, nil)
.then(^id(id result){
    return asyncTaskC(result);
}, nil)
.then(^id(id result){
    return asyncTaskD(result);
}, nil)
.then(^id(id result){
    // handle result
    return nil;
}, nil);
.then(nil, ^id(NSError*error) {
    NSLog(@“”Error: %@“, error);
    return nil;
});

This akin to the probably more familiar synchronous style with exception handling:

try {
    id a = A();
    id b = B(a);
    id c = C(b);
    id d = D(c);
    // handle d
}
catch (NSError* error) {
    NSLog(@“”Error: %@“, error);
}

Promises in general have other useful features:

For example, having a reference to a promise, via then one can "register" as many handlers as desired. In RXPromise, registering handlers can occur at any time and from any thread since it is fully thread-safe.

RXPromise has a couple more useful functional features, not required by the Promise/A+ specification. One is "cancellation".

It turned out that “cancellation” is an invaluable and important feature. For example a call-site holding a reference to a promise can send it the cancel message in order to indicate that it’s no longer interested in the eventual result.

Just imagine an asynchronous task which loads an image from the web and which shall be displayed in a view controller. If the user moves away from the current view controller, the developer may implement code which sends a cancel message to the imagePromise, which in turn triggers the error handler defined by the HTTP Request Operation where the request will be cancelled.

In RXPromise, a cancel message will only be forwarded from a parent to its children, but not vice versa. That is, a “root” promise will cancel all children promises. But a child promise will only cancel the “branch” where it is the parent. The cancel message will also be forwarded to children if a promise has been already resolved.

An asynchronous task can itself register handler for its own promise, and thus can detect when someone else cancelled it. It may then prematurely stop performing a possibly lengthy and costly task.

Here are a couple other implementations of Promises in Objective-C found on GitHub:

https://github.com/Schoonology/aplus-objc
https://github.com/affablebloke/deferred-objective-c
https://github.com/bww/FutureKit
https://github.com/jkubicek/JKPromises
https://github.com/Strilanc/ObjC-CollapsingFutures
https://github.com/b52/OMPromises
https://github.com/mproberts/objc-promise
https://github.com/klaaspieter/Promise
https://github.com/jameswomack/Promise
https://github.com/nilfs/promise-objc
https://github.com/mxcl/PromiseKit
https://github.com/apleshkov/promises-aplus
https://github.com/KptainO/Rebelle

and my own implementation: RXPromise.

This list is likely not complete!

When choosing a third library for your project, please check carefully if the implementation of the library follows the prerequisites listed below:

  • A reliable promise library SHALL be thread safe!

    It’s all about asynchronous processing, and we want to utilize multiple CPUs and execute on different threads concurrently whenever possible. Be careful, most of the implementations are not thread safe!

  • Handlers SHALL be called asynchronously which respect to the call-site! Always, and no matter what!

    Any decent implementation should also follow a very strict pattern when invoking the asynchronous functions. Many implementors tend to "optimize" the case, where a handler will be invoked synchronously when the promise is already resolved when the handler will registered. This can cause all sorts of issues. See Don't release Zalgo!.

  • There should also be a mechanism to cancel a promise.

    The possibility to cancel an asynchronous task often becomes a requirement with high priority in the requirement analysis. If not, for sure there will be filed an enhancement request from a user some time later after the app has been released. The reason should be obvious: any task which may stall or take too long to finish, should be cancelable by the user or by a timeout. A decent promise library should support cancellation.

1
  • 1
    This gets the prize for longest non-answer ever. But A for effort :-) Apr 28, 2015 at 20:43
3

I realize this is an old question but I have to answer it because my answer is different than the others.

For those that say it's a matter of personal preference, I have to disagree. There is a good, logical, reason to prefer one over the other...

In the completion case, your block is handed two objects, one represents success while the other represents failure... So what do you do if both are nil? What do you do if both have a value? These are questions that can be avoided at compile time and as such they should be. You avoid these questions by having two separate blocks.

Having separate success and failure blocks makes your code statically verifiable.


Note that things change with Swift. In it, we can implement the notion of an Either enum so that the single completion block is guaranteed to have either an object or an error, and must have exactly one of them. So for Swift, a single block is better.

1

I suspect it's going to end up being personal preference...

But I prefer the separate success/failure blocks. I like separating the success/failure logic. If you had nested success/failures, you'd end up with something that would be more readable (in my opinion at least).

As a relatively extreme example of such nesting, here's some Ruby showing this pattern.

5
  • 1
    I've seen nested chains of both. I think they both look terrible, but that's my personal opinion. Jan 23, 2013 at 18:52
  • 1
    But how else could you chain async calls? Jan 23, 2013 at 18:56
  • I don't know man… I don't know. Part of the reason I'm asking is because I don't like how any of my async code looks. Jan 23, 2013 at 19:13
  • Sure. You end up writing your code in continuation-passing style, which isn't terribly surprising. (Haskell has its do notation for exactly this reason: letting you write in an ostensibly direct style.) Jan 23, 2013 at 19:18
  • You may be interested in this ObjC Promises implementation: github.com/couchdeveloper/RXPromise
    – e1985
    Sep 13, 2013 at 8:21
0

This feels like a complete copout, but I don't think there is a right answer here. I went with the completion block simply because error handling may still need to be done in the success condition when using success/failure blocks.

I think the final code will look something like

[target taskWithCompletion:^(id object, NSError *error) {
    if (error) {
        // Oh noes! report the failure.
    } else if (![target validateObject:&object error:&error]) {
        // Oh noes! report the failure.
    } else {
        // W00t! I've got my object
    }
}];

or simply

[target taskWithCompletion:^(id object, NSError *error) {
    if (error || ![target validateObject:&object error:&error]) {
        // Oh noes! report the failure.
        return;
    }

    // W00t! I've got my object
}];

Not the best chunk of code and nesting it gets worse

[target taskWithCompletion:^(id object, NSError *error) {
    if (error || ![target validateObject:&object error:&error]) {
        // Oh noes! report the failure.
        return;
    }

    [object objectTaskWithCompletion:^(id object2, NSError *error) {
        if (error || ![object validateObject2:&object2 error:&error]) {
            // Oh noes! report the failure.
            return;
        }

        // W00t! I've got object and object 2
    }];
}];

I think I'll go mope for a while.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.