Im trying to understand completion handlers & blocks. I believe you can use blocks for many deep programming things without completion handlers, but I think i understand that completion handlers are based on blocks. (So basically completion handlers need blocks but not the other way around).
So I saw this code on the internet about the old twitter framework:
[twitterFeed performRequestWithHandler:^(NSData *responseData, NSHTTPURLResponse *urlResponse, NSError *error) {
if (!error) {
self.successLabel.text = @"Tweeted Successfully";
[self playTweetSound];
} else {
// Show alert
}
// Stop indicator
sharedApplication.networkActivityIndicatorVisible = NO;
}];
Here we are calling a method which does stuff (performs TWRequest) and returns when finished with responseData & urlResponse & error. Only when it returns does it execute the block which tests granted and stops the activity indicator. PERFECT!
Now this is a setup I have for a different app which works, but I'm trying to put the pieces together:
@interface
Define an ivar
typedef void (^Handler)(NSArray *users);
Declare the method
+(void)fetchUsersWithCompletionHandler:(Handler)handler;
@implementation
+(void)fetchUsersWithCompletionHandler:(Handler)handler {
//...Code to create NSURLRequest omitted...
__block NSArray *usersArray = [[NSArray alloc] init];
//A. Executes the request
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
// Peform the request
NSURLResponse *response;
NSError *error = nil;
NSData *receivedData = [NSURLConnection sendSynchronousRequest:request
returningResponse:&response
error:&error];
// Deal with your error
if (error) {
}
NSLog(@"Error %@", error);
return;
}
// Else deal with data
NSString *responseString = [[NSString alloc] initWithData:receivedData encoding:NSUTF8StringEncoding];
usersArray = [NSJSONSerialization JSONObjectWithData:[responseString dataUsingEncoding:NSASCIIStringEncoding] options:0 error:nil];
// Checks for handler & returns usersArray to main thread - but where does handler come from & how does it know to wait tip usersArray is populated?
if (handler){
dispatch_sync(dispatch_get_main_queue(), ^{
handler(usersArray);
});
}
});
}
Here is my understanding:
But basically, the request is executed and the error is dealt with, the data is dealt with and then, the handler is checked. My question is, how does this handler part work? I understand if it exists then it will send back to the main queue and return the usersArray. But how does it know to wait until usersArray is populated? I guess whats confusing me is that fact that the method:block in this case has another block inside of it, the dispatch_async call. I guess what Im looking for is the logic that actually does stuff and knows WHEN to return the responseData and urlResponse. I know its not the same app, but I cant see the code for performRequestWithHandler.
As an example, many functions that start an asynchronous operation take a closure argument as a completion handler. The function returns after it starts the operation, but the closure isn't called until the operation is completed—the closure needs to escape, to be called later.
Swift Closures with Completion handler Closures are self-contained blocks of functionality that can be passed around and used in your code. Said differently, a closure is a block of code that you can assign to a variable. You can then pass it around in your code, for instance to another function.
The block to execute after the operation's main task is completed.
Basically in this case it works like that:
Since it is dispath_async, current thread leaves the fetchUsersWithCompletionHandler: method.
...
time passes, till background queue has some free resources
...
And now, when the background queue is free, it consumes scheduled operation (Note: It performs synchronous request - so it waits for data):
NSURLResponse *response;
NSError *error = nil;
NSData *receivedData = [NSURLConnection sendSynchronousRequest:request
returningResponse:&response
error:&error];
...
Once data comes, then the usersArray is populated.
Code continues to this part:
if (handler){
dispatch_sync(dispatch_get_main_queue(), ^{
handler(usersArray);
});
}
Now, if we have handler specified, it schedules block for invocation on a main queue. It is dispatch_sync, so execution on current thread won't proceed till main thread will be done with the block. At this point, background thread patiently waits.
...
another moment passes
...
Now main queue has some free resources, so it consumes above block, and executes this code (passing previously populated usersArray to the 'handler'):
handler(usersArray);
Once it is done, it returns from the block and continues consuming whatever it is in the main queue.
Edit: As for the questions you asked:
It's not like main/background queue will be always busy, it's just it may be. (assuming background queue does not support concurrent operations like the main one). Imagine following code, that is being executed on a main thread:
dispatch_async(dispatch_get_main_queue(), ^{
//here task #1 that takes 10 seconds to run
NSLog(@"Task #1 finished");
});
NSLog(@"Task #1 scheduled");
dispatch_async(dispatch_get_main_queue(), ^{
//here task #2 that takes 5s to run
NSLog(@"Task #2 finished");
});
NSLog(@"Task #2 scheduled");
Since both are dispatch_async
calls, you schedule these for execution one after another. But task #2 won't be processed by main queue immediately, since first it has to leave current execution loop, secondly, it has to first finish task #1.
So the log output will be like that:
Task #1 scheduled
Task #2 scheduled
Task #1 finished
Task #2 finished
2.You have:
typedef void (^Handler)(NSArray *users);
Which declares block typedefe'd as Handler
that has void
return type and that accepts NSArray *
as parameter.
Later, you have your function:
+(void)fetchUsersWithCompletionHandler:(Handler)handler
Which takes as a parameter block of type Handler
and allow access to it using local name handler
.
And step #8:
handler(usersArray);
Which just directly calls handler
block (like you were calling any C/C++ function) and passes usersArray
as a parameter to it.
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