I want to call a method which will return a value from its completion handler. The method performs asynchronously and I don't want to return a value before all the body of the method has been executed. Here is some faulty code to illustrate what I am trying to achieve:
// This is the way I want to call the method
NSDictionary *account = [_accountModel getCurrentClient];
// This is the faulty method that I want to fix
- (NSDictionary *)getCurrentClient
{
__block NSDictionary *currentClient = nil;
NXOAuth2Account *currentAccount = [[[NXOAuth2AccountStore sharedStore] accounts] lastObject];
[NXOAuth2Request performMethod:@"GET"
onResource:[NSURL URLWithString:[NSString stringWithFormat:@"%@/clients/%@", kCatapultHost, currentAccount.userData[@"account_name"]]]
usingParameters:nil
withAccount:currentAccount
sendProgressHandler:nil
responseHandler:^ (NSURLResponse *response, NSData *responseData, NSError *error) {
NSError *jsonError;
currentClient = [NSJSONSerialization JSONObjectWithData:responseData
options:kNilOptions
error:&jsonError];
}];
return currentClient;
}
I don't want the getCurrentClient
method to return a value until the NXOAuth2Request
has finished. I can't return the current client inside the response handler of the request either. So what are my options?
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.
A completion handler is nothing more than a regular closure that's called to indicate that some piece of work has completed or produced a result. The completion handler that you pass to the dataTask function (in this case via trailing closure syntax) is called once the data task completes.
A completion handler in Swift is a function that calls back when a task completes. This is why it is also called a callback function. A callback function is passed as an argument into another function. When this function completes running a task, it executes the callback function.
This means that the completion closure won't be executed until after makeRequest(_:) has exited its scope and the closure outlives it. In short, @escaping is used to inform callers of a function that takes a closure that the closure might be stored or otherwise outlive the scope of the receiving function.
You need to change getCurrentClient
to take in a completion block instead of returning a value.
For example:
-(void)getCurrentClientWithCompletionHandler:(void (^)(NSDictionary* currentClient))handler
{
NXOAuth2Account *currentAccount = [[[NXOAuth2AccountStore sharedStore] accounts] lastObject];
[NXOAuth2Request performMethod:@"GET"
onResource:[NSURL URLWithString:[NSString stringWithFormat:@"%@/clients/%@", kCatapultHost, currentAccount.userData[@"account_name"]]]
usingParameters:nil
withAccount:currentAccount
sendProgressHandler:nil
responseHandler:^ (NSURLResponse *response, NSData *responseData, NSError *error) {
NSError *jsonError;
NSDictionary* deserializedDict = [NSJSONSerialization JSONObjectWithData:responseData
options:kNilOptions
error:&jsonError];
handler(deserializedDict);
}];
}
It's important to remember that getCurrentClient
will return immediately, while the network request is dispatched on another thread. Don't forget that if you want to update the UI using your response handler, you need to have your handler run on the main thread.
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