How to retry a block based URL Request
Asked Answered
F

2

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!

Filip answered 5/5, 2014 at 17:32 Comment(1)
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.Justification
H
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];

}
Headgear answered 5/5, 2014 at 17:40 Comment(3)
Similar question. What if this has to be a class method and some parameters have to be passed to it?Chantal
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.Metathesize
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.Headgear
G
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];
Gman answered 5/5, 2014 at 17:47 Comment(3)
the retry should be exponential back off + jitterHutment
@Avt: In this case, if web service fails after retrycounter expires, this method will not return error info IMO which it should.Thyroiditis
@Thyroiditis 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.Gman
H
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];

}
Headgear answered 5/5, 2014 at 17:40 Comment(3)
Similar question. What if this has to be a class method and some parameters have to be passed to it?Chantal
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.Metathesize
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.Headgear

© 2022 - 2024 — McMap. All rights reserved.