13

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?

4

3 回答 3

12

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 ... 
   }];
于 2013-08-21T17:52:39.093 回答
7

I generally use the second implementation. A few reasons for it:

  1. It works better for re-use blocks. I often have the same implementation for failures so I can just create a method that returns the failure block and change the success block as necessary. You could do this with the first implementation but you would need nested blocks then.

  2. I like to avoid unnecessary checks when possible ("if error then do something with my error otherwise do something with my object"). Since I will either get an object or an error (never both), I find it inconvenient to pass in both parameters when only one will ever be valid.

  3. I use Restkit and I just need to pass the blocks into my API calls and it will automatically call the success or failure block when the call comes in. If I do the second implementation I need to handle the method callbacks (failure or success method/block) and execute the block manually. I am not sure about AFNetworking though.

I personally think both are fine but in this situation I would argue for the second.

于 2013-08-21T16:39:41.753 回答
3

In my opinion it's very subjective. Thinking of use case scenarios for your API would the user benefit more from a completion or success/fail?

If its something like a network access API ala AFNetworking probably go with success/fail - its more appropriate. However if the API is likely to finish successfully every time i.e. an animation block then completion is best

于 2013-08-21T16:38:37.813 回答