AFNetworking - Как настроить запросы для повторного использования в случае тайм-аута?
Недавно я перешел из ASIHTTPRequest в AFNetworking, что было здорово. Тем не менее, сервер, с которым я связываюсь, имеет некоторые проблемы и иногда вызывает мои запросы на тайм-аут. При использовании ASIHTTPRequest было возможно настроить счетчик повторов на запрос в случае таймаута, используя следующий селектор
-setNumberOfTimesToRetryOnTimeout:
Об этом можно еще упомянуть в этой статье, Можно ли повторить попытку ASIHTTPRequest?
Это AFNetworking, если вы незнакомы
https://github.com/AFNetworking/AFNetworking#readme
Мне не удалось найти эквивалентный api в AFNetworking, кто-нибудь нашел решение для повторных сетевых запросов в случае таймаута с использованием AFNetworking?
Ответы
Ответ 1
Мэтт Томпсон, разработчик AFNetworking, был достаточно любезен, чтобы ответить на этот вопрос для меня. Ниже приведена ссылка github, объясняющая решение.
https://github.com/AFNetworking/AFNetworking/issues/393
В принципе, AFNetworking не поддерживает эту функцию. Разработчику остается реализовать его в каждом конкретном случае, как показано ниже (взято из ответа Мэтта Томпсона на github)
- (void)downloadFileRetryingNumberOfTimes:(NSUInteger)ntimes
success:(void (^)(id responseObject))success
failure:(void (^)(NSError *error))failure
{
if (ntimes <= 0) {
if (failure) {
NSError *error = ...;
failure(error);
}
} else {
[self getPath:@"/path/to/file" parameters:nil success:^(AFHTTPRequestOperation *operation, id responseObject) {
if (success) {
success(...);
}
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
[self downloadFileRetryingNumberOfTimes:ntimes - 1 success:success failure:failure];
}];
}
}
Ответ 2
В моем классе ApiClient я реализовал частный метод:
- (void)sendRequest:(NSURLRequest *)request successBlock:(void (^)(AFHTTPRequestOperation *operation, id responseObject))successBlock failureBlock:(void (^)(AFHTTPRequestOperation *operation, NSError *error))failureBlock
{
__block NSUInteger numberOfRetries = 3;
__block __weak void (^weakSendRequestBlock)(void);
void (^sendRequestBlock)(void);
weakSendRequestBlock = sendRequestBlock = ^{
__strong typeof (weakSendRequestBlock)strongSendRequestBlock = weakSendRequestBlock;
numberOfRetries--;
AFHTTPRequestOperation *operation = [self.httpManager HTTPRequestOperationWithRequest:request success:successBlock failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSInteger statusCode = [[[error userInfo] objectForKey:AFNetworkingOperationFailingURLResponseErrorKey] statusCode];
if (numberOfRetries > 0 && (statusCode == 500 || statusCode == 502 || statusCode == 503 || statusCode == 0)) {
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 2 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{
strongSendRequestBlock();
});
} else {
if (failureBlock) {
failureBlock(operation, error);
}
}
}];
[self.httpManager.operationQueue addOperation:operation];
};
sendRequestBlock();
}
Пример использования:
- (void)getSomeDetails:(DictionaryResultBlock)block
{
if (!block) {
return;
}
NSString *urlString = @"your url string";
NSMutableURLRequest *request = [self.httpManager.requestSerializer requestWithMethod:@"POST" URLString:[[NSURL URLWithString:urlString relativeToURL:self.defaultUrl] absoluteString] parameters:nil error:nil];
// Configure you request here
[request setValue:version forHTTPHeaderField:@"client-version"];
NSMutableDictionary *bodyParams = @{};
[request setHTTPBody:[NSJSONSerialization dataWithJSONObject:bodyParams options:0 error:nil]];
[self sendRequest:request successBlock:^(AFHTTPRequestOperation *operation, id responseObject) {
id response = [NSJSONSerialization JSONObjectWithData:responseObject options:0 error:nil];
block(response, nil);
} failureBlock:^(AFHTTPRequestOperation *operation, NSError *error) {
block(nil, error);
}];
}
Ответ 3
В моем случае мне часто требовалась функция повтора, поэтому я подобрал эту категорию retry policy, которая поможет вам с этим AFNetworking + RetryPolicy
Что касается AFNetworking 3.0, он может хорошо служить.
Ответ 4
В соответствии с вашими ответами вы можете сделать что-то еще более общее (и сложное), используя блок, принимающий в качестве параметра блок:
typedef void (^CallbackBlock)(NSError* error, NSObject* response);
- (void) performBlock:(void (^)(CallbackBlock callback)) blockToExecute retryingNumberOfTimes:(NSUInteger)ntimes onCompletion:(void (^)(NSError* error, NSObject* response)) onCompletion {
blockToExecute(^(NSError* error, NSObject* response){
if (error == nil) {
onCompletion(nil, response);
} else {
if (ntimes <= 0) {
if (onCompletion) {
onCompletion(error, nil);
}
} else {
[self performBlock:blockToExecute retryingNumberOfTimes:(ntimes - 1) onCompletion:onCompletion];
}
};
});
}
Затем окружайте ваши асинхронные HTTP-запросы следующим образом:
[self performBlock:^(CallbackBlock callback) {
[...]
AFHTTPRequestOperationManager *manager = [WSManager getHTTPRequestOperationManager];
[manager POST:base parameters:parameters success:^(AFHTTPRequestOperation *operation, id responseObject) {
dispatch_async(dispatch_get_main_queue(), ^(void){
if (callback) {
callback(nil, responseObject);
}
});
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
if (callback) {
NSError* errorCode = [[NSError alloc] initWithDomain:AppErrorDomain code:[operation.response statusCode] userInfo:@{ NSLocalizedDescriptionKey :error.localizedDescription}];
callback(errorCode, nil);
}
}];
} retryingNumberOfTimes:5 onCompletion:^(NSError *error, NSObject* response) {
//everything done
}];
Таким образом, повторы ожидают завершения HTTP-запроса, и вам не нужно реализовывать цикл повтора в каждом методе запроса.