One vs. Two Completion Blocks

I use a network kit that uses the twoBlock approach, but I prefer to use oneBlock in my own code. Which bothers me if the twoBlock approach twoBlock better. For some reason I donโ€™t see this.

Is there a way one approach is better than another?


oneBlock

1 block that combines data and errors:

 -(void)oneBlock { [self startWithCompletionBlock:^(id obj, NSError* error) { if(error) { NSLog(@"error: %@", error); } else { NSLog(@"success: %@", obj); } }]; } 

two-block approach

2 blocks for processing data and errors:

 -(void)twoBlocks { [self startWithCompletionBlock:^(id obj) { NSLog(@"success: %@", obj); } errorBlock:^(NSError* error) { NSLog(@"error: %@", error); }]; } 
+4
source share
5 answers

I do not think you can say that one of them is better. There is just another balance of pros and cons.

The main advantage of the two-block approach is that you get the best separation of code for a โ€œhappyโ€ path and code for error management. (This separation is similar to one of the advantages provided by using exceptions, but it is another beast, because catch blocks allow you to collect in one place, that is, outside your "functional" block, all the code to control a bunch of possible error conditions that may arise inside " functional "block and the control of which will usually be scattered throughout, in the above example with two blocks there is nothing like this, since the code for controlling the error condition still seems mixed with the rest of the code of your fun tion).

On the other hand, it is quite possible that in both cases, i.e. successes and failures, you would like to take some common action. Consider, for example, serializing a sequence of network operations: when one operation completes, you perform the next operation, both during the first successful operation and in the event of a failure. This is certainly the case when you will have some code replication if you use a two-block approach.

In general, I donโ€™t think that there is a big difference, since you can easily do what you need to do with both approaches, but in specific cases one approach may fit your workflow better than another.

Only my 2 cents.

+4
source

I find the two-block approach cleaner. You do not need if / else to block, so the separation of error handling is improved. It is also 1 line less. Not a big difference overall, but it helps to keep the code a little tidier and easier to read, that's all.

Another thing that, in my opinion, makes a 2-block better, is that error handling is automatically completed. I prefer the code to be in the โ€œDo it all, except when something is wrongโ€ section to โ€œassume that something went wrong!โ€ "No, continue." style. Maybe I'm an optimist. In any case, I would prefer to see the important material at the top and the problem with the error.

+1
source

I prefer # 1. I think it should be up to the client code to decide what the actual error is and what it means in the current context, based on the returned NSError instance.

In option No. 2, if the completion block contains more than several lines of code, which is probably used, for example, in the view controller, there is a high probability that you want to execute many of the same completion code in two blocks, regardless of whether an error occurred or not (updating the user interface, restoring the state, etc.). This will lead to unnecessary code duplication.

In addition, option # 1 is less than the code if you are not interested in the error.

+1
source

I settled on a two-block approach. Benefits:

  • It allows you to return an object and an error if it is ever needed.
  • No question about the order or one or both can be called
  • If you add a third variable to another callback, everything is much less randomly

In my mind, several blocks must be reserved for several consecutive callbacks. Think about how UIView animation works.

+1
source

For the reason @sergio, I think the oneBlock approach is cleaner. This gives the subscriber great flexibility in managing codes. With the callback API, there is often a cleanup code (or the next step) that should be called at the end of the callback, whether it succeeds or not:

 -(void)oneBlock { [self startWithCompletionBlock:^(id obj, NSError* error) { if (error) { NSLog(@"error: %@", error); } else { NSLog(@"success: %@", obj); } self.connection = nil; dispatch_async(dispatch_get_main_queue(), ^{ [self updateUI]; }); }]; } 

Also, if the success block is long, twoBlocks just reads poorly:

 -(void)twoBlocks { [self startWithCompletionBlock:^(id obj) { [self doSomething]; [self doSomethingElse]; [self setUpSomeOtherRequestWithCompletionBlock:^(id obj) { [self doSomething]; [self doSomethingElse]; NSLog(@"inside request succeeded"); } errorBlock:^(NSError* error) { NSLog(@"error: %@", error); }]; dispatch_async(dispatch_get_main_queue(), ^{ [self updateUI]; }); } errorBlock:^(NSError* error) { NSLog(@"error: %@", error); }]; } 
+1
source

All Articles