1

I am a beginner in Objective C and I am looking to do two consecutive HTTP GETs (one after the other). What I have got so far is that I have a NSURLSessionDataTask inside the completion block of the first NSURLSessionDataTask. This is causing my code to be a bit unreadable, so I was wondering what is a better way to do this? Here is some sample code:

{
    NSURLSessionConfiguration *config = [NSURLSessionConfiguration defaultSessionConfiguration];    
    NSURLSession *session = [NSURLSession sessionWithConfiguration:config];

    NSMutableURLRequest *url_request_1 = [NSMutableURLRequest requestWithURL:@"some_url_1"];
    [url_request_1 setHTTPMethod:@"GET"];

    NSURLSessionDataTask *url_task_1 = [session
        dataTaskWithRequest:url_request_1
        completionHandler:^(NSData *data1,
        NSURLResponse *response1,
        NSError *error1) {

            if(data1 !=nil){
                // Evaluate some_url_2 from the response of url_task_1

                NSMutableURLRequest *url_request_2 = [NSMutableURLRequest requestWithURL:@"some_url_2"];
                [url_request_2 setHTTPMethod:@"GET"];

                NSURLSessionDataTask *url_task_2 = [session
                     dataTaskWithRequest:url_request_2
                     completionHandler:^(NSData *data2,
                     NSURLResponse *response2,
                     NSError *error2) {

                        if(data2 !=nil){      
                            // Process data here                 
                        } else {
                            // Handle error here.
                            return;
                        }
                    }];

                [urlRequest2 resume];
            }
            else{
                // Handle error here
                return;
            }
        }];

    [url_task_1 resume];
}
rmaddy
  • 314,917
  • 42
  • 532
  • 579
Tywin
  • 21
  • 2
  • @Velox - Manually altering the indentation of the question may have made it more readable, but you are now obscuring one of the underlying problems of how Tywin wrote his code. – Rob Nov 29 '15 at 18:36

1 Answers1

1

This is made a little less unwieldy by changing your indentation style and using early-exit pattern.

- (void)performRequestsWithCompletion:(void (^ _Nonnull)(NSDictionary *, NSError *))completion {
    NSMutableURLRequest *request1 = [NSMutableURLRequest requestWithURL:firstURL];

    NSURLSessionDataTask *task1 = [self.session dataTaskWithRequest:request1 completionHandler:^(NSData *data1, NSURLResponse *response1, NSError *error1) {
        if (!data1) {
            // handle error here, then return
            completion(nil, error1);
            return;
        }

        NSMutableURLRequest *request2 = [NSMutableURLRequest requestWithURL:secondURL];

        NSURLSessionDataTask *task2 = [self.session dataTaskWithRequest:request2 completionHandler:^(NSData *data2, NSURLResponse *response2, NSError *error2) {
            if (!data2) {
                // handle error here, then return
                completion(nil, error2);
                return;
            }

            // handle parsing `data2` here
            NSDictionary *result = ...;
            completion(result, nil);
        }];

        [task2 resume];
    }];

    [task1 resume];
}

Note, I added a completion handler here, because that's one of the best patterns to let whatever initiated this request that everything is done. Clearly, my block parameters assumed you would be returning a dictionary, so you should change this to be whatever type(s) your routine returns.

Alternatively (and especially if you're doing a lot more than just two successive web service calls), you can break this down into separate methods:

- (void)performRequestsWithCompletion:(void (^ _Nonnull)(NSDictionary *, NSError *))completion {
    [self performFirstRequestWithCompletion:completion];
}

- (NSURLSessionTask *)performFirstRequestWithCompletion:(void (^ _Nonnull)(NSDictionary *, NSError *))completion {
    NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:firstURL];

    NSURLSessionTask *task = [self.session dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
        if (!data || error) {
            // Handle error here
            completion(nil, error);
            return;
        }

        // Evaluate some_url_2 from the response of url_task_1
        [self performSecondRequestWithCompletion:completion];
    }];
    [task resume];

    return task;
}

- (NSURLSessionTask *)performSecondRequestWithCompletion:(void (^ _Nonnull)(NSDictionary *, NSError *))completion {
    NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:secondURL];

    NSURLSessionTask *task = [self.session dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
        if (!data || error) {
            // Handle error here, and return
            completion(nil, error);
            return;
        }

        // Process data here

        NSDictionary *result = ...;
        completion(result, nil);
    }];

    [task resume];

    return task;
}

With this pattern, no matter how many dependent calls you have, you won't end up with the tower of nested blocks.


In the interest of completeness, other patterns that avoid these towers of blocks-within-blocks include NSOperation patterns and third party approaches like futures/promises (e.g. PromiseKit). Those are beyond the scope of this question, but they're worth considering if you're doing this a lot.

Rob
  • 415,655
  • 72
  • 787
  • 1,044
  • Alright, I suppose breaking it apart into separate methods would make more sense. Hopefully this is good practice. Thanks Rob! – Tywin Nov 29 '15 at 18:46
  • I prefer your second scenario. It's good to break two task in two different methods. If I add `dispatch_async(dispatch_get_main_queue()` in NSURLSessionDataTask will it create problem? – Mihir Oza May 11 '17 at 09:58
  • Not only will dispatching the to the main queue not cause any problems, but it is also a very common practice, making updating UI and model objects much easier. This is a great pattern as long as you never block the main thread (which you should never do, anyway). Personally, I'd generally use completion handler blocks in this pattern, too. – Rob May 11 '17 at 18:28