When designing a block-based API in ObjC, which approach is better, one completion block or two, one each for success and failure?
Let's say we have a method retrieving something asynchronously to a block, with one completion block it would be:
- (void) retrieveSomethingCompletion:(void (^)(id retrievedObject, NSError *error))completionBlock;
And with success/failure blocks (AFNetworking style):
- (void) retrieveSomethingSuccess:(void(^)(id retrievedObject))successBlock failure:(void(^)(NSError *error))failureBlock;
I always use the second approach, but what are the pro/cons of each option? What do you use normally and why?
Both are fine patterns (and I up voted both Firo's and John's answers because -- yes, it is a matter of taste and both of their answers are spot on), but there are several distinct advantages to going with a single block, in my experience. John Woods raises an excellent point about the "flavor" of the API, though I would claim that a network operation always completes (unless it has no timeout, which is a different class of bug) and that the success/failure modality is oft not quite either/or.
It eliminates having to duplicate the code between the blocks that is common to tearing down the task on completion regardless of success or failure.
It provides a single conceptual execution flow between scheduling the task and knowing when the task is completed. When this task is completed, the completion block will be called.
In some situations, a failure may actually produce data that should be processed in a fashion like the success path. In fewer situations, a successful completion may actually carry along an error. While the NSError**
pattern on methods is purely either/or, one of the advantages of using either block pattern is that this can be expressed. The further advantage of using a single completion block is that you avoid duplicated logic.
Multiple blocks as parameters to methods is flat out ugly and annoying. There is a reason why the coding pattern is only pass one block to a method and always make that block the last parameter. Unfortunately, even the system APIs don't consistently follow this pattern, though the use is limited mostly to cases where the blocks tend to be simple. Mostly.
It avoids nonsense like this:
[foo doSomething: ^{
.... lots of code ...
.... lots of code ...
.... lots of code ...
.... lots of code ...
.... lots of code ...
.... lots of code ...
.... lots of code ...
.... lots of code ...
.... lots of code ...
.... lots of code ...
.... lots of code ...
.... lots of code ...
.... lots of code ...
.... lots of code ...
.... lots of code ...
.... lots of code ...
.... lots of code ...
} andSomethingElse: ^{
.... lots of code ...
.... lots of code ...
.... lots of code ...
.... lots of code ...
.... lots of code ...
.... lots of code ...
.... lots of code ...
.... lots of code ...
.... lots of code ...
.... lots of code ...
.... lots of code ...
.... lots of code ...
.... lots of code ...
.... lots of code ...
.... lots of code ...
.... lots of code ...
.... lots of code ...
.... lots of code ...
}];
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With