Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save jparise/f3402e181ce79cb40968b7bb082b3b43 to your computer and use it in GitHub Desktop.
Save jparise/f3402e181ce79cb40968b7bb082b3b43 to your computer and use it in GitHub Desktop.
AFHTTPRequestOperationManager (Retries)
//
// AFHTTPRequestOperationManager+Retries.h
// Pinterest
//
// 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
//
// AFHTTPRequestOperationManager+Retries.m
// Pinterest
//
// 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