12

I am fetching data using iOS7's new URL request methods, like so:

NSMutableURLRequest *request = [NSMutableURLRequest
    requestWithURL:[NSURL URLWithString:[self.baseUrl 
    stringByAppendingString:path]]];

NSURLSessionDataTask *dataTask = [[NSURLSession sharedSession] dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
    NSHTTPURLResponse* httpResponse = (NSHTTPURLResponse*)response;
    NSUInteger responseStatusCode = [httpResponse statusCode];

    if (responseStatusCode != 200) { 
        // RETRY (??????) 
    } else       
        completionBlock(results[@"result"][symbol]);
}];
[dataTask resume];

Unfortunately, from time to time I get HTTP responses indicating the server is not reachable (response code != 200) and need to resend the same request to the server.

How can this be done? How would I need to complete my code snippet above where my comment // RETRY is?

In my example I call the completion block after a successful fetch. But how can I send the same request again?

Thank you!

Matteo Gobbi
  • 17,697
  • 3
  • 27
  • 41
AlexR
  • 5,514
  • 9
  • 75
  • 130
  • Is it worth retrying on most status codes? It seems like the only status code worth retrying on is 500. I'm brand new to this so I'm curious about retrying outside of connection failure or a server error. – Eric Mentele Mar 23 '16 at 23:37

2 Answers2

16

It is better to have a retry counter to prevent your method from running forever:

- (void)someMethodWithRetryCounter:(int) retryCounter
{
    if (retryCounter == 0) {
        return;
    }
    retryCounter--;

    NSMutableURLRequest *request = [NSMutableURLRequest
                                    requestWithURL:[NSURL URLWithString:[self.baseUrl
                                                                         stringByAppendingString:path]]];

     __weak __typeof(self)weakSelf = self;

    NSURLSessionDataTask *dataTask = [[NSURLSession sharedSession] dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
        NSHTTPURLResponse* httpResponse = (NSHTTPURLResponse*)response;
        NSUInteger responseStatusCode = [httpResponse statusCode];

        if (responseStatusCode != 200) {
            [weakSelf someMethodWithRetryCounter: retryCounter];
        } else
            completionBlock(results[@"result"][symbol]);
    }];
    [dataTask resume];
}

It should be called following way:

[self someMethodWithRetryCounter:5];
Avt
  • 16,927
  • 4
  • 52
  • 72
  • 2
    the retry should be exponential back off + jitter – hariszaman Nov 23 '16 at 08:55
  • @Avt: In this case, if web service fails after retrycounter expires, this method will not return error info IMO which it should. – i.AsifNoor Sep 11 '17 at 08:48
  • 2
    @i.AsifNoor An error should be returned in a completion block. However, in the question the completion block 'completionBlock' has only one param. To make my idea clear I have decided not to make my code as close as possible to the original one. – Avt Sep 11 '17 at 11:39
14

Put your request code in a method and call it again in a dispatch_async block ;)

- (void)requestMethod {

    NSMutableURLRequest *request = [NSMutableURLRequest
                                    requestWithURL:[NSURL URLWithString:[self.baseUrl
                                                                         stringByAppendingString:path]]];

    __weak typeof (self) weakSelf = self;
    NSURLSessionDataTask *dataTask = [[NSURLSession sharedSession] dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
        NSHTTPURLResponse* httpResponse = (NSHTTPURLResponse*)response;
        NSUInteger responseStatusCode = [httpResponse statusCode];

        if (responseStatusCode != 200) {
            dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0ul), ^{
                [weakSelf requestMethod];
            });
        } else       
            completionBlock(results[@"result"][symbol]);
    }];
    [dataTask resume];

}
Matteo Gobbi
  • 17,697
  • 3
  • 27
  • 41
  • Similar question. What if this has to be a class method and some parameters have to be passed to it? – Zia May 05 '14 at 17:57
  • 1
    Is a separate queue necessary ? `[weakSelf selectorName]` will do right ? Also having a upper threshold for retry attempt would prevent infinite loops in case server went down. – GoodSp33d May 05 '14 at 18:09
  • For avoid infinite loop you can simply use a counter variable. For the queue you can put it in any queue you want, by use dispatch_async so the function return immediately. – Matteo Gobbi May 05 '14 at 18:24