Created
June 9, 2016 20:29
-
-
Save jparise/f3402e181ce79cb40968b7bb082b3b43 to your computer and use it in GitHub Desktop.
AFHTTPRequestOperationManager (Retries)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// AFHTTPRequestOperationManager+Retries.h | |
// | |
// Created by Jon Parise on 5/20/13. | |
// Copyright (c) 2013 Pinterest. All rights reserved. | |
// | |
#import "AFHTTPRequestOperationManager.h" | |
#import "AFHTTPRequestOperation.h" | |
@interface AFHTTPRequestOperationManager (Retries) | |
/** | |
Creates an `AFHTTPRequestOperation` that can be retried on failure. | |
@param urlRequest The request object to be loaded asynchronously during execution of the operation. | |
@param retryAfter A block object to be executed if the request operation fails to succeed. This block can return a positive relative time delay value indicating when the request operation should be retried. A negative return value indicates that the operation should not be retried, in which case the `failure` block will be invoked. | |
@param success A block object to be executed when the request operation finishes successfully. This block has no return value and takes two arguments: the created request operation and the object created from the response data of request. | |
@param failure A block object to be executed when the request operation finishes unsuccessfully, or that finishes successfully, but encountered an error while parsing the response data. This block has no return value and takes two arguments: the created request operation and the `NSError` object describing the network or parsing error that occurred. | |
*/ | |
- (AFHTTPRequestOperation *)HTTPRequestOperationWithRequest:(NSURLRequest *)urlRequest | |
retryAfter:(NSTimeInterval (^)(AFHTTPRequestOperation *operation, NSError *error))retryAfter | |
success:(void (^)(AFHTTPRequestOperation *operation, id responseObject))success | |
failure:(void (^)(AFHTTPRequestOperation *operation, NSError *error))failure; | |
/** | |
Creates an `AFHTTPRequestOperation` that will be retried on failure. | |
An unsuccessful operation is considered "retriable" if its HTTP status code falls within `+HTTPRequestOperationWithRequest:retriableStatusCodes`. | |
@param urlRequest The request object to be loaded asynchronously during execution of the operation. | |
@param retryDelay The initial number of seconds to wait between retries. This value is increased exponentially for each retry attempt. | |
@param retryLimit The maximum number of retries that will be attempt before giving up and returning failure. | |
@param success A block object to be executed when the request operation finishes successfully. This block has no return value and takes two arguments: the created request operation and the object created from the response data of request. | |
@param failure A block object to be executed when the request operation finishes unsuccessfully, or that finishes successfully, but encountered an error while parsing the response data. This block has no return value and takes two arguments:, the created request operation and the `NSError` object describing the network or parsing error that occurred. | |
*/ | |
- (AFHTTPRequestOperation *)HTTPRequestOperationWithRequest:(NSURLRequest *)urlRequest | |
retryDelay:(NSTimeInterval)retryDelayInSeconds | |
retryLimit:(NSUInteger)retryLimit | |
success:(void (^)(AFHTTPRequestOperation *operation, id responseObject))success | |
failure:(void (^)(AFHTTPRequestOperation *operation, NSError *error))failure; | |
@end | |
@interface AFHTTPRequestOperation (Retries) | |
/** | |
Returns an `NSIndexSet` object containing the ranges of retriable HTTP status codes. | |
By default, this is a set containing 408 and the range 500 to 599, inclusive. | |
*/ | |
+ (NSIndexSet *)retriableStatusCodes; | |
/** | |
Adds status codes to the set of retriable HTTP status codes returned by `+retriableStatusCodes` in subsequent calls by this class and its descendants. | |
@param statusCodes The status codes to be added to the set of retriable HTTP status codes. | |
*/ | |
+ (void)addRetriableStatusCodes:(NSIndexSet *)statusCodes; | |
@end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// AFHTTPRequestOperationManager+Retries.m | |
// | |
// Created by Jon Parise on 5/20/13. | |
// Copyright (c) 2013 Pinterest. All rights reserved. | |
// | |
#import "AFHTTPRequestOperationManager+Retries.h" | |
#import <objc/runtime.h> | |
@implementation AFHTTPRequestOperationManager (Retries) | |
- (AFHTTPRequestOperation *)HTTPRequestOperationWithRequest:(NSURLRequest *)urlRequest | |
retryAfter:(NSTimeInterval (^)(AFHTTPRequestOperation *operation, NSError *error))retryAfter | |
success:(void (^)(AFHTTPRequestOperation *operation, id responseObject))success | |
failure:(void (^)(AFHTTPRequestOperation *operation, NSError *error))failure; | |
{ | |
if (!retryAfter) { | |
return [self HTTPRequestOperationWithRequest:urlRequest success:success failure:failure]; | |
} | |
// Use the provided retry block to evaluate whether or not the failed operation | |
// should be retried. If the retry block returns a non-negative time interval, | |
// it will be used to determine when the operation will be re-attempted. | |
void (^retriableFailure)(AFHTTPRequestOperation *, NSError *, id) = ^(AFHTTPRequestOperation *operation, NSError *error, id callback) { | |
NSTimeInterval delayInSeconds = retryAfter(operation, error); | |
if (delayInSeconds >= 0.0) { | |
dispatch_time_t when = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC)); | |
dispatch_after(when, dispatch_get_main_queue(), ^{ | |
void (^innerFailure)(AFHTTPRequestOperation *, NSError *) = ^(AFHTTPRequestOperation *operation, NSError *error){ | |
void (^callbackBlock)(AFHTTPRequestOperation *, NSError *, id) = callback; | |
callbackBlock(operation, error, callback); | |
}; | |
AFHTTPRequestOperation *newOperation = [self HTTPRequestOperationWithRequest:urlRequest | |
success:success | |
failure:innerFailure]; | |
[self.operationQueue addOperation:newOperation]; | |
}); | |
} else { | |
if (failure) { | |
failure(operation, error); | |
} | |
} | |
}; | |
// If the initial request fails, we pass control to our wrapped failure block. | |
// The wrapped block takes a reference to itself as a parameter in order to | |
// support easily reference-counted recursive block calls. | |
void (^initialFailure)(AFHTTPRequestOperation *, NSError *) = ^(AFHTTPRequestOperation *operation, NSError *error) { | |
retriableFailure(operation, error, retriableFailure); | |
}; | |
// Return the initial HTTP operation for this request. | |
return [self HTTPRequestOperationWithRequest:urlRequest success:success failure:initialFailure]; | |
} | |
- (AFHTTPRequestOperation *)HTTPRequestOperationWithRequest:(NSURLRequest *)urlRequest | |
retryDelay:(NSTimeInterval)retryDelayInSeconds | |
retryLimit:(NSUInteger)retryLimit | |
success:(void (^)(AFHTTPRequestOperation *operation, id responseObject))success | |
failure:(void (^)(AFHTTPRequestOperation *operation, NSError *error))failure { | |
__block NSUInteger retries = 0; | |
// This block evaluates the failed operation's result and determines whether or not | |
// to attempt a retry. An exponentially-increasing delay (based on retryDelayInSeconds) | |
// is imposed between retry attempts. This block is re-invoked for each failure. | |
NSTimeInterval (^retryBlock)(AFHTTPRequestOperation *, NSError *) = ^NSTimeInterval(AFHTTPRequestOperation *operation, NSError *error) { | |
NSUInteger statusCode = [operation.response statusCode]; | |
if (retries < retryLimit && [[[operation class] retriableStatusCodes] containsIndex:statusCode]) { | |
retries++; | |
return retryDelayInSeconds * pow(2, retries); | |
} | |
return -1.0; | |
}; | |
// Return the initial HTTP operation for this request. | |
return [self HTTPRequestOperationWithRequest:urlRequest retryAfter:retryBlock success:success failure:failure]; | |
} | |
@end | |
@implementation AFHTTPRequestOperation (Retries) | |
+ (NSIndexSet *)retriableStatusCodes { | |
NSMutableIndexSet *statusCodes = [NSMutableIndexSet indexSet]; | |
[statusCodes addIndex:408]; // Request Timeout | |
[statusCodes addIndexesInRange:NSMakeRange(500, 100)]; // Server Errors | |
return statusCodes; | |
} | |
+ (void)addRetriableStatusCodes:(NSIndexSet *)statusCodes { | |
NSMutableIndexSet *allStatusCodes = [[NSMutableIndexSet alloc] initWithIndexSet:[self retriableStatusCodes]]; | |
[allStatusCodes addIndexes:statusCodes]; | |
Method originalMethod = class_getClassMethod(self, @selector(retriableStatusCodes)); | |
IMP implementation = imp_implementationWithBlock(^(__unused id _self) { return allStatusCodes; }); | |
class_replaceMethod(objc_getMetaClass([NSStringFromClass(self) UTF8String]), | |
@selector(retriableStatusCodes), implementation, method_getTypeEncoding(originalMethod)); | |
} | |
@end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment