Как установить тайм-аут с AFNetworking

79

В моем проекте используется AFNetworking.

https://github.com/AFNetworking/AFNetworking

Как уменьшить время ожидания? Банкомат без подключения к Интернету блокировка сбоя не срабатывает примерно 2 минуты. До долгого ....

Jennas
источник
2
Я настоятельно не рекомендую любое решение, которое пытается переопределить интервалы тайм-аута, особенно те, которые используются performSelector:afterDelay:...для ручной отмены существующих операций. Пожалуйста, смотрите мой ответ для более подробной информации.
Мэтт

Ответы:

110

Изменение интервала тайм-аута почти наверняка не лучшее решение проблемы, которую вы описываете. Вместо этого кажется, что на самом деле вам нужно, чтобы HTTP-клиент обрабатывал сеть, которая становится недоступной, не так ли?

AFHTTPClientуже имеет встроенный механизм , чтобы вы знаете , когда подключение к Интернету теряется, -setReachabilityStatusChangeBlock:.

В медленных сетях запросы могут занимать много времени. Лучше доверять iOS, чтобы знать, как справляться с медленными соединениями, и отличать это от отсутствия соединения.


Чтобы расширить мои рассуждения о том, почему следует избегать других подходов, упомянутых в этой ветке, вот несколько мыслей:

  • Запросы можно отменить еще до их начала. Постановка запроса в очередь не гарантирует, когда он действительно начнется.
  • Интервалы тайм-аута не должны отменять длительные запросы, особенно POST. Представьте, что вы пытаетесь загрузить или загрузить видео размером 100 МБ. Если запрос выполняется максимально эффективно в медленной сети 3G, зачем вам без нужды останавливать его, если это занимает немного больше времени, чем ожидалось?
  • Это performSelector:afterDelay:...может быть опасно в многопоточных приложениях. Это открывает перед непонятными и трудными для отладки условиями гонки.
Мэтт
источник
Я считаю, что это потому, что, несмотря на название, вопрос заключается в том, как как можно быстрее потерпеть неудачу в случаях «без интернета». Правильный способ сделать это - использовать достижимость. Использование тайм-аутов либо не работает, либо может привести к появлению трудно заметных / исправленных ошибок.
Михай Тимар
2
@mattt, хотя я согласен с вашим подходом, я борюсь с проблемой подключения, когда мне действительно нужна функция тайм-аута. Дело в том, что я взаимодействую с устройством, которое предоставляет "точку доступа Wi-Fi". Когда я подключаюсь к этой «сети Wi-Fi», то с точки зрения доступности я не подключаюсь, хотя могу выполнять запросы к устройству. С другой стороны, я действительно хочу иметь возможность определять, доступно ли само устройство. Есть предположения? Спасибо
Ставаш
42
хорошие моменты, но не отвечает на вопрос о том, как установить тайм-аут
Макс Маклауд
3
В справочнике AFNetworking: «Доступность сети - это диагностический инструмент, который можно использовать, чтобы понять, почему запрос мог быть неудачным. Его не следует использовать для определения того, делать ли запрос или нет». в разделе setReachabilityStatusChangeBlock. Поэтому я думаю, что setReachabilityStatusChangeBlock - не решение ...
LKM,
1
Я понимаю, почему мы должны дважды подумать, чтобы вручную установить тайм-аут, но этот принятый ответ не отвечает на вопрос. Мое приложение должно как можно скорее узнать о потере интернет-соединения. Используя AFNetworking, ReachabilityManager не обнаруживает случай, когда устройство подключено к точке доступа Wi-Fi, но сама точка доступа теряет интернет (часто требуется несколько минут, чтобы обнаружить это). Так что отправка запроса в Google с таймаутом ~ 5 секунд кажется моим лучшим вариантом в этом сценарии. Если что-то мне не хватает?
Таннер Семерад,
44

Я настоятельно рекомендую посмотреть на ответ Мэтта выше - хотя этот ответ не противоречит проблемам, которые он упоминает в целом, для исходного вопроса о плакатах проверка достижимости гораздо лучше подходит.

Однако, если вы все же хотите установить тайм-аут (без всех проблем, присущих и performSelector:afterDelay:т. Д., То запрос на перенос, упомянутый Lego, описывает способ сделать это как один из комментариев, вам просто нужно сделать:

NSMutableURLRequest *request = [client requestWithMethod:@"GET" path:@"/" parameters:nil];
[request setTimeoutInterval:120];

AFHTTPRequestOperation *operation = [client HTTPRequestOperationWithRequest:request success:^{...} failure:^{...}];
[client enqueueHTTPRequestOperation:operation];

но см. предостережение @KCHarwood упоминает, что, похоже, Apple не разрешает изменять это для запросов POST (что исправлено в iOS 6 и выше).

Как указывает @ChrisopherPickslay, это не общий тайм-аут, это тайм-аут между получением (или отправкой данных). Я не знаю ни одного разумного способа сделать общий тайм-аут. В документации Apple для setTimeoutInterval говорится:

Интервал тайм-аута в секундах. Если во время попытки подключения запрос остается бездействующим дольше интервала тайм-аута, считается, что для запроса истекло время ожидания. По умолчанию интервал ожидания составляет 60 секунд.

JosephH
источник
Да, я пробовал, и он не работает при выполнении запроса POST.
borisdiakur 06
7
примечание: Apple исправила это в iOS 6
Raptor
2
Это не делает то, что вы предлагаете. timeoutIntervalэто таймер простоя, а не тайм-аут запроса. Таким образом, вам нужно будет вообще не получать данных в течение 120 секунд, чтобы указанный выше код истек. Если данные поступают медленно, запрос может продолжаться бесконечно.
Кристофер Пикслей,
Справедливо, что я не особо много рассказывал о том, как это работает - я, надеюсь, добавил эту информацию сейчас, спасибо!
JosephH
26

Вы можете установить интервал тайм-аута с помощью метода requestSerializer setTimeoutInterval. Вы можете получить requestSerializer из экземпляра AFHTTPRequestOperationManager.

Например, чтобы сделать почтовый запрос с таймаутом 25 секунд:

    NSDictionary *params = @{@"par1": @"value1",
                         @"par2": @"value2"};

    AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];

    [manager.requestSerializer setTimeoutInterval:25];  //Time out after 25 seconds

    [manager POST:@"URL" parameters:params success:^(AFHTTPRequestOperation *operation, id responseObject) {

    //Success call back bock
    NSLog(@"Request completed with response: %@", responseObject);


    } failure:^(AFHTTPRequestOperation *operation, NSError *error) {
     //Failure callback block. This block may be called due to time out or any other failure reason
    }];
Мостафа Абделлатиф
источник
7

Я думаю, вам нужно исправить это вручную.

Я создаю подкласс AFHTTPClient и изменил

- (NSMutableURLRequest *)requestWithMethod:(NSString *)method path:(NSString *)path parameters:(NSDictionary *)parameters

метод, добавив

[request setTimeoutInterval:10.0];

в строке 236 AFHTTPClient.m. Конечно, было бы хорошо, если бы это можно было настроить, но, насколько я понимаю, в настоящее время это невозможно.

Корнелиус
источник
7
Еще одна вещь, которую следует учитывать, заключается в том, что Apple отменяет тайм-аут для POST. Автоматически что-то вроде 4 минут, я думаю, и вы НЕ МОЖЕТЕ это изменить.
kcharwood
Я тоже это вижу. Почему это так? Нехорошо заставлять пользователя ждать 4 минуты, прежде чем произойдет сбой соединения.
slatvick
2
Чтобы добавить в ответ @KCHarwood. Начиная с iOS 6, Apple не отменяет тайм-аут публикации. Это было исправлено в iOS 6.
ADAM,
7

Наконец выяснил, как это сделать с помощью асинхронного POST-запроса:

- (void)timeout:(NSDictionary*)dict {
    NDLog(@"timeout");
    AFHTTPRequestOperation *operation = [dict objectForKey:@"operation"];
    if (operation) {
        [operation cancel];
    }
    [[AFNetworkActivityIndicatorManager sharedManager] decrementActivityCount];
    [self perform:[[dict objectForKey:@"selector"] pointerValue] on:[dict objectForKey:@"object"] with:nil];
}

- (void)perform:(SEL)selector on:(id)target with:(id)object {
    if (target && [target respondsToSelector:selector]) {
        [target performSelector:selector withObject:object];
    }
}

- (void)doStuffAndNotifyObject:(id)object withSelector:(SEL)selector {
    // AFHTTPRequestOperation asynchronous with selector                
    NSDictionary *params = [NSDictionary dictionaryWithObjectsAndKeys:
                            @"doStuff", @"task",
                            nil];

    AFHTTPClient *httpClient = [[AFHTTPClient alloc] initWithBaseURL:[NSURL URLWithString:baseURL]];

    NSMutableURLRequest *request = [httpClient requestWithMethod:@"POST" path:requestURL parameters:params];
    [httpClient release];

    AFHTTPRequestOperation *operation = [[[AFHTTPRequestOperation alloc] initWithRequest:request] autorelease];

    NSDictionary *dict = [NSDictionary dictionaryWithObjectsAndKeys:
                          operation, @"operation", 
                          object, @"object", 
                          [NSValue valueWithPointer:selector], @"selector", 
                          nil];
    [self performSelector:@selector(timeout:) withObject:dict afterDelay:timeout];

    [operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {            
        [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(timeout:) object:dict];
        [[AFNetworkActivityIndicatorManager sharedManager] decrementActivityCount];
        [self perform:selector on:object with:[operation responseString]];
    }
    failure:^(AFHTTPRequestOperation *operation, NSError *error) {
        NDLog(@"fail! \nerror: %@", [error localizedDescription]);
        [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(timeout:) object:dict];
        [[AFNetworkActivityIndicatorManager sharedManager] decrementActivityCount];
        [self perform:selector on:object with:nil];
    }];

    NSOperationQueue *queue = [[[NSOperationQueue alloc] init] autorelease];
    [[AFNetworkActivityIndicatorManager sharedManager] incrementActivityCount];
    [queue addOperation:operation];
}

Я протестировал этот код, позволив своему серверу sleep(aFewSeconds).

Если вам нужно выполнить синхронный запрос POST, НЕ используйте [queue waitUntilAllOperationsAreFinished];. Вместо этого используйте тот же подход, что и для асинхронного запроса, и дождитесь срабатывания функции, которую вы передаете в аргументе селектора.

борисдиакур
источник
18
Нет-нет-нет-нет, пожалуйста , не используйте это в реальном приложении. Фактически ваш код отменяет запрос по истечении интервала времени, который начинается при создании операции, а не при ее запуске . Это могло привести к отмене запросов еще до их запуска.
Мэтт
5
@mattt Пожалуйста, предоставьте образец кода, который работает. На самом деле то, что вы описываете, - это именно то, что я хочу сделать: я хочу, чтобы временной интервал начал отсчитываться прямо в тот момент, когда я создаю операцию.
borisdiakur
Спасибо, Лего! Я использую AFNetworking, и примерно в 10% случаев мои операции из AFNetworking случайным образом никогда не вызывают блоки успеха или отказа [сервер находится в США, тестовый пользователь находится в Китае]. Для меня это большая проблема, поскольку я намеренно блокирую части пользовательского интерфейса, пока эти запросы выполняются, чтобы пользователь не отправлял слишком много запросов одновременно. В конце концов я реализовал версию, основанную на этом решении, которая передает блок завершения в качестве параметра с помощью функции performselector withdelay и обеспечивает выполнение этого блока, если! Operation.isFinished - Мэтт: Спасибо за AFNetworking!
Кори
5

Основываясь на ответах других и предложении @mattt по связанным с проектом проблемам, вот быстрый совет, если вы подклассифицируете AFHTTPClient:

@implementation SomeAPIClient // subclass of AFHTTPClient

// ...

- (NSMutableURLRequest *)requestWithMethod:(NSString *)method path:(NSString *)path parameters:(NSDictionary *)parameters {
  NSMutableURLRequest *request = [super requestWithMethod:method path:path parameters:parameters];
  [request setTimeoutInterval:120];
  return request;
}

- (NSMutableURLRequest *)multipartFormRequestWithMethod:(NSString *)method path:(NSString *)path parameters:(NSDictionary *)parameters constructingBodyWithBlock:(void (^)(id <AFMultipartFormData> formData))block {
  NSMutableURLRequest *request = [super requestWithMethod:method path:path parameters:parameters];
  [request setTimeoutInterval:120];
  return request;
}

@end

Протестировано для работы на iOS 6.

Гурпартап Сингх
источник
0

Разве мы не можем сделать это с помощью такого таймера:

В файле .h

{
NSInteger time;
AFJSONRequestOperation *operation;
}

В файле .m

-(void)AFNetworkingmethod{

    time = 0;

    NSTtimer *timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(startTimer:) userInfo:nil repeats:YES];
    [timer fire];


    operation = [AFJSONRequestOperation JSONRequestOperationWithRequest:request success:^(NSURLRequest *request, NSHTTPURLResponse *response, id JSON) {
        [self operationDidFinishLoading:JSON];
    } failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error, id JSON) {
        [self operationDidFailWithError:error];
    }];
    [operation setJSONReadingOptions:NSJSONReadingMutableContainers];
    [operation start];
}

-(void)startTimer:(NSTimer *)someTimer{
    if (time == 15&&![operation isFinished]) {
        time = 0;
        [operation invalidate];
        [operation cancel];
        NSLog(@"Timeout");
        return;
    }
    ++time;
}
Улаш Санчак
источник
0

Здесь есть два разных значения определения «тайм-аута».

Тайм-аут как в timeoutInterval

Вы хотите отбросить запрос, когда он бездействует (передача прекращается) дольше произвольного интервала времени. Пример: вы установили timeoutInterval10 секунд, вы запускаете свой запрос в 12:00:00, он может передавать некоторые данные до 12:00:23, затем соединение будет отключено в 12:00:33. Этот случай охвачен почти всеми ответами здесь (включая Джозефа Х, Мостафу Абделлатифа, Корнелиуса и Гурпартапа Сингха).

Тайм-аут как в timeoutDeadline

Вы хотите отбросить запрос, когда он достигнет крайнего срока, который произойдет позже. Пример: вы устанавливаете deadline10 секунд в будущем, вы запускаете свой запрос в 12:00:00, он может пытаться передать некоторые данные до 12:00:23, но соединение прервется раньше, в 12:00:10. Этот случай покрыт борисдиакуром.

Я хотел бы показать, как реализовать этот крайний срок в Swift (3 и 4) для AFNetworking 3.1.

let sessionManager = AFHTTPSessionManager(baseURL: baseURL)
let request = sessionManager.post(endPoint, parameters: parameters, progress: { ... }, success: { ... }, failure: { ... })
// timeout deadline at 10 seconds in the future
DispatchQueue.global().asyncAfter(deadline: .now() + 10.0) {
    request?.cancel()
}

И чтобы дать тестируемый пример, этот код должен печатать «отказ» вместо «успех» из-за немедленного тайм-аута в 0,0 секунды в будущем:

let sessionManager = AFHTTPSessionManager(baseURL: URL(string: "https://example.com"))
sessionManager.responseSerializer = AFHTTPResponseSerializer()
let request = sessionManager.get("/", parameters: nil, progress: nil, success: { _ in
    print("success")
}, failure: { _ in
    print("failure")
})
// timeout deadline at 0 seconds in the future
DispatchQueue.global().asyncAfter(deadline: .now() + 0.0) {
    request?.cancel()
}
Cœur
источник
-2

Согласитесь с Мэттом, вам не следует пытаться изменить timeoutInterval. Но вы также не должны полагаться на проверку доступности, чтобы решить, когда вы собираетесь установить соединение, вы не узнаете, пока не попробуете.

Как указано в документе Apple:

Как правило, вы не должны использовать короткие интервалы времени ожидания, а вместо этого должны предоставить пользователю простой способ отменить длительную операцию. Для получения дополнительной информации прочтите «Проектирование для реальных сетей».

гав
источник