What does the completion handler execute the block when your task is interesting?

I requested and tried to understand how completion handlers work. I used a lot and I read a lot of textbooks. I will post the one that I use here, but I want to be able to create my own without using another user's code as a link.

I understand this completion handler where this caller method is:

-(void)viewDidLoad{ [newSimpleCounter countToTenThousandAndReturnCompletionBLock:^(BOOL completed){ if(completed){ NSLog(@"Ten Thousands Counts Finished"); } }]; } 

and then in the called method:

 -(void)countToTenThousandAndReturnCompletionBLock:(void (^)(BOOL))completed{ int x = 1; while (x < 10001) { NSLog(@"%i", x); x++; } completed(YES); } 

Then I tried this option based on many SO posts:

 - (void)viewDidLoad{ [self.spinner startAnimating]; [SantiappsHelper fetchUsersWithCompletionHandler:^(NSArray *users) { self.usersArray = users; [self.tableView reloadData]; }]; } 

which will reload the table view by the data recipients after calling this method:

 typedef void (^Handler)(NSArray *users); +(void)fetchUsersWithCompletionHandler:(Handler)handler { NSURL *url = [NSURL URLWithString:@"http://www.somewebservice.com"]; NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url cachePolicy:NSURLRequestReloadIgnoringLocalAndRemoteCacheData timeoutInterval:10]; [request setHTTPMethod: @"GET"]; **// We dispatch a queue to the background to execute the synchronous NSURLRequest** dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{ // Perform the request NSURLResponse *response; NSError *error = nil; NSData *receivedData = [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&error]; if (error) { **// If an error returns, log it, otherwise log the response** // Deal with your error if ([response isKindOfClass:[NSHTTPURLResponse class]]) { NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse*)response; NSLog(@"HTTP Error: %d %@", httpResponse.statusCode, error); return; } NSLog(@"Error %@", error); return; } **// So this line won't get processed until the response from the server is returned?** NSString *responseString = [[NSString alloc] initWithData:receivedData encoding:NSUTF8StringEncoding]; NSArray *usersArray = [[NSArray alloc] init]; usersArray = [NSJSONSerialization JSONObjectWithData:[responseString dataUsingEncoding:NSASCIIStringEncoding] options:0 error:nil]; // Finally when a response is received and this line is reached, handler refers to the block passed into this called method...so it dispatches back to the main queue and returns the usersArray if (handler){ dispatch_sync(dispatch_get_main_queue(), ^{ handler(usersArray); }); } }); } 

I see this in the counter example that the called method (with the passed block) will never exit the loop until it is executed. So the part of the “completion” actually depends on the code inside the called method, and not on the block that it passed?

In this case, the “completion” part depends on the fact that the NSURLRequest call is synchronous. What if it was asynchronous? How can I refrain from calling a block until my data is filled with NSURLResponse?

+3
source share
2 answers

Your first example is the correct and complete and best way to understand completion blocks. There is no extra magic for them. They are never automatically executed. They are executed when some piece of code calls them.

As you noticed, in the last example it is easy to call the completion block at the right time, because everything is synchronous. If it was asynchronous, you need to save the block in the instance variable and call it when the asynchronous operation completes. You should be informed of the completion of the operation (possibly using the completion handler).

Be careful when you store the unit in ivar. One of your examples includes:

  self.usersArray = users; 

Calling self will cause the block to save self (the calling object). This can easily create a save loop. Generally, you need to weakly reference self as follows:

 - (void)viewDidLoad{ [self.spinner startAnimating]; __weak typeof(self) weakSelf = self; [SantiappsHelper fetchUsersWithCompletionHandler:^(NSArray *users) { typeof(self) strongSelf = weakSelf; if (strongSelf) { [strongSelf setUsersArray:users]; [[strongSelf tableView] reloadData]; } }]; } 

This is a rather pedantic version of the weakSelf / strongSelf template, in which case it can be made a little easier, but it shows all the parts you might need. You are weak on self to create a persistence loop. Then in the entire block you take a strong link so that self doesn’t disappear on you in the middle of your block. Then you will see that self actually still exists, and only then continue. (Since nil messaging is legal, you could skip the strongSelf step in this particular case and it would be the same.)

+3
source

Your first example (countToTenThousandAndReturnCompletionBLock) is actually a synchronous method. The completion handler does not make much sense here: as an alternative, you can call this block immediately after the hypothetical method countToTenThousand (which is basically the same, only without the completion handler).

The second example of fetchUsersWithCompletionHandler: is an asynchronous method. However, it is actually quite suboptimal:

  • It must somehow signal to the client site that the request may be unsuccessful. That is, either provide an additional parameter to the completion handler, for example. " NSError* error or we have one parameter id result . In the first case, either the error or the array is not nil , and in the second case, the result of a single parameter can be either an error object (it is an NSError view) or the actual result ( NSArray view).

  • If your request fails, you skip the error message on the call site.

  • There are code smells:

    In fact, the underlying network code implemented by the system is asynchronous. However, the sendSynchronousRequest: convenience class method used is synchronous. This means that as part of the sendSynchronousRequest: implementation sendSynchronousRequest: calling thread blocks until the result of a network response is received. And this_blocking_ takes up a whole chain just for waiting. Creating a stream is quite expensive, and there is waste only for this purpose. This is the first smell of code. Yes, just using a convenient method of the sendSynchronousRequest: class sendSynchronousRequest: bad programming in itself praxis!

    Then, in your code, you execute this synchronous request again asynchronously, sending it to the queue.

    So, you better use an asynchronous method (e.g. sendAsynchronous... ) for a network request that supposedly signals completion through a completion handler. This completion handler can then call your completion handler parameter, taking care of whether you received the actual result or error.

+2
source

All Articles