iosobjective-cobjective-c-blocks

Block with completion and arguments


I am new to blocks. I am trying to analyze how the following code works.

As i understand this specific method has a block and returns a

NSURLSessionDataTask

. getTotalFollowersFrom is the name of the method. (NSString*)influencerId is the userId that is passed when calling this method. WithCompletion: is used so we know when the method completes the Api call. Void is what the block returns. The caret symbol (^) is used to define the block. The following (id _Nullable likesCountRequestResponse, NSError * _Nullable error)completion are arguments. This is what i do not understand. According to documentation arguments have return values inside the block. I see the return result; that returns the NSURLSessionDataTask but i do not understand how the values return for the arguments of the block. What am i missing? Could anyone please explain to me?

- (NSURLSessionDataTask *)getTotalLikesFrom:(NSString*)userId withCompletion:(void (^)(id _Nullable likesCountRequestResponse, NSError * _Nullable error))completion {
    
    NSString* postString = [NSString stringWithFormat:@"apicall/%@", userId];
    
    @weakify(self)
    NSURLSessionDataTask *result = [self GET:postString parameters:nil completion:^(OVCResponse * _Nullable response, NSError * _Nullable error) {
        
        @strongify(self)
        [self handleResponse:response error:error adjustErrorBlock:self.commonValidationErrorAdjustBlock completion:completion];
        
    }];
    
    return result;
}

- (void)handleResponse:(nullable OVCResponse *)response error:(nullable NSError *)error adjustErrorBlock:(nullable ApiClientv2AdjustErrorBlock)adjustErrorBlock completion:(void (^)(id _Nullable result, NSError * _Nullable error))completion 
{ 
if (response.HTTPResponse.statusCode >= 500) 
{
error = nil != error ? error : NSError.com_eight_APIServiceUnknownError; [SentrySDK captureError:error];
}
else 
{
error = nil != error ? error : NSError.com_eight_APIServiceUnknownError;
}
id result = nil == error ? response.result : nil;
completion(result, error);
} 

Solution

  • Your example is not complete. So, let's consider a MCVE:

    - (NSURLSessionDataTask *)networkRequestWithCompletion:(void (^)(NSDictionary * _Nullable object, NSError * _Nullable error))completion {
        NSURL *url = [NSURL URLWithString:@"https://httpbin.org/get"];
    
        NSURLSessionDataTask *task = [NSURLSession.sharedSession dataTaskWithURL:url completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
            if (!data || error) {
                completion(nil, error);
                return;
            }
    
            NSError *parseError;
            NSDictionary *resultObject = [NSJSONSerialization JSONObjectWithData:data options:kNilOptions error:&parseError];
            if (parseError) {
                completion(nil, parseError);
                return;
            }
    
            completion(resultObject, nil);
        }];
    
        [task resume];
    
        return task;
    }
    

    This performs a network request and parses the JSON. Inside that method, you will see references to:

    completion(nil, error);
    

    Or:

    completion(resultObject, nil);
    

    That is how the data is passed back to the caller. The method supplies the parameters of the block when it calls completion . Thus, this method can be supplied a block and use these two parameters:

    [self networkRequestWithCompletion:^(NSDictionary *dictionary, NSError *error) {
        if (error) {
            NSLog(@"error = %@", error);
            return;
        }
    
        // you can access the `dictionary` parameter here ...
    
        NSLog(@"dictionary = %@", dictionary);
    }];
    
    // ... but you cannot reference the `dictionary` or `error` parameters here
    // because the above runs asynchronously (i.e., later).
    

    In the code snippet you supplied in your question, you are not calling completion anywhere. But notice that it is supplied to handleResponse. That method is undoubtedly calling the block for you.


    As a matter of personal preference, I think that the choice of result for the NSURLSessionDataTask is a little confusing. So in my example, I gave it a better name, task, to make it clear that it is the NSURLSessionTask object, not the result of the network request.

    But what is the purpose of this object? It is so that the caller has the option to cancel the request if it wants. For example, you might have:

    @interface ViewController ()
    @property (nonatomic, weak) NSURLSessionTask *task;
    @end
    
    @implementation ViewController
    
    - (void)viewDidLoad {
        [super viewDidLoad];
    
        self.task = [self networkRequestWithCompletion:^(NSDictionary *dictionary, NSError *error) {
            if (error) {
                NSLog(@"error = %@", error);
                return;
            }
    
            NSLog(@"dictionary = %@", dictionary);
        }];
    }
    
    - (IBAction)didTapCancelButton:(id)sender {
        [self.task cancel];
    }
    
    ...
    
    @end
    

    So, the NSURLSessionTask reference is returned, which we save, so that we can have, for example, a cancel button that will cancel that asynchronous network request.

    But, in short, do not conflate the NSURLSessionTask reference that is immediately returned by networkRequestWithCompletion (to give us the option to cancel the request later) with the parameters of the block, which we use to supply the caller with the results of the network request.