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

Мой проект использует AFNetworking.

https://github.com/AFNetworking/AFNetworking

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


person jennas    schedule 29.11.2011    source источник
comment
Я настоятельно не рекомендую любое решение, которое пытается переопределить интервалы времени ожидания, особенно те, которые используют performSelector:afterDelay:... для ручной отмены существующих операций. Пожалуйста, смотрите мой ответ для более подробной информации.   -  person mattt    schedule 09.04.2012


Ответы (9)


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

AFHTTPClient уже имеет встроенный механизм оповещения о потере интернет-соединения, -setReachabilityStatusChangeBlock:.

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


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

  • Запросы могут быть отменены еще до того, как они будут запущены. Постановка запроса в очередь не гарантирует, когда он действительно запустится.
  • Интервалы тайм-аута не должны отменять длительные запросы, особенно POST. Представьте, что вы пытаетесь загрузить или загрузить видео размером 100 МБ. Если запрос выполняется наилучшим образом в медленной сети 3G, зачем вам без необходимости останавливать его, если он занимает немного больше времени, чем ожидалось?
  • Выполнение performSelector:afterDelay:... может быть опасным в многопоточных приложениях. Это открывает перед вами непонятные и трудно отлаживаемые условия гонки.
person mattt    schedule 08.04.2012
comment
Я считаю, что это потому, что вопрос, несмотря на его название, заключается в том, как потерпеть неудачу как можно быстрее в случаях отсутствия Интернета. Правильный способ сделать это — использовать достижимость. Использование тайм-аутов либо не работает, либо имеет высокую вероятность появления труднозаметных/исправляемых ошибок. - person Mihai Timar; 14.09.2013
comment
@mattt, хотя я согласен с вашим подходом, я борюсь с проблемой подключения, когда мне действительно нужна функция тайм-аута. Дело в том, что я взаимодействую с устройством, которое предоставляет точку доступа Wi-Fi. При подключении к этой сети WiFi с точки зрения доступности я не подключен, хотя могу выполнять запросы к устройству. С другой стороны, я хочу иметь возможность определить, доступно ли само устройство. Есть предположения? Спасибо - person Stavash; 13.11.2013
comment
@mattt Я попытался создать AFNetworkReachabilityManager для определенного адреса, хотя, похоже, возникла проблема с обратным вызовом - хотя я настраиваю блок изменения статуса, networkReachabilityStatusBlock равен нулю к тому времени, когда в startMonitoring происходит обратный вызов. Я открою вопрос через gitHub. - person Stavash; 13.11.2013
comment
Открыт как выпуск № 1590. - person Stavash; 13.11.2013
comment
хорошие моменты, но не отвечает на вопрос, как установить тайм-аут - person Max MacLeod; 05.12.2013
comment
В справочнике AFNetworking доступность сети — это диагностический инструмент, который можно использовать для понимания того, почему запрос мог завершиться неудачно. Его не следует использовать для определения того, делать запрос или нет. в разделе «setReachabilityStatusChangeBlock». Поэтому я думаю, что setReachabilityStatusChangeBlock — это не решение... - person LKM; 04.06.2015
comment
Я понимаю, почему мы должны дважды подумать о ручной установке тайм-аута, но этот принятый ответ не отвечает на вопрос. Мое приложение должно как можно скорее узнать, когда интернет-соединение потеряно. Используя AFNetworking, ReachabilityManager не обнаруживает случай, когда устройство подключено к точке доступа Wi-Fi, но сама точка доступа теряет интернет (часто для обнаружения этого требуется несколько минут). Таким образом, отправка запроса в Google с тайм-аутом ~ 5 секунд кажется моим лучшим вариантом в этом сценарии. Если я что-то упускаю? - person Tanner Semerad; 27.11.2015

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

Однако, если вы все же хотите установить тайм-аут (без всех проблем, присущих 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 секунд.

person JosephH    schedule 03.01.2012
comment
Да, я пробовал, и это не работает при выполнении POST-запроса. - person borisdiakur; 06.01.2012
comment
примечание: Apple исправила это в iOS 6. - person Raptor; 05.03.2013
comment
Это не делает то, что вы предлагаете. timeoutInterval — это таймер простоя, а не время ожидания запроса. Таким образом, вам придется вообще не получать никаких данных в течение 120 секунд, чтобы приведенный выше код истек. Если данные поступают медленно, запрос может продолжаться бесконечно. - person Christopher Pickslay; 12.11.2013
comment
Это справедливое замечание, что я не очень много говорил о том, как это работает - надеюсь, я добавил эту информацию сейчас, спасибо! - person JosephH; 12.11.2013

Вы можете установить интервал времени ожидания с помощью метода setTimeoutInterval requestSerializer. Вы можете получить 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
    }];
person Mostafa Abdellateef    schedule 16.07.2014

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

Я подклассифицирую AFHTTPClient и изменил

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

метод путем добавления

[request setTimeoutInterval:10.0];

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

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

Наконец-то выяснил, как это сделать с помощью асинхронного 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];. Вместо этого используйте тот же подход, что и для асинхронного запроса, и дождитесь запуска функции, которую вы передаете в аргументе селектора.

person borisdiakur    schedule 07.01.2012
comment
Нет, нет, нет, пожалуйста, не используйте это в реальном приложении. На самом деле ваш код отменяет запрос через интервал времени, который начинается при создании операции, не при ее запуске. Это может привести к отмене запросов еще до их запуска. - person mattt; 09.04.2012
comment
@mattt Пожалуйста, предоставьте пример кода, который работает. На самом деле то, что вы описываете, именно то, что я хочу, чтобы произошло: я хочу, чтобы временной интервал начал тикать прямо в тот момент, когда я создаю операцию. - person borisdiakur; 09.04.2012
comment
Спасибо, Лего! Я использую AFNetworking, и примерно в 10% случаев мои операции из AFNetworking никогда не вызывают блоки успеха или отказа [сервер находится в США, тестовый пользователь находится в Китае]. Это большая проблема для меня, поскольку я намеренно блокирую части пользовательского интерфейса, пока выполняются эти запросы, чтобы пользователь не мог отправить слишком много запросов одновременно. В конце концов я реализовал версию, основанную на этом решении, которая передает блок завершения в качестве параметра через селектор выполнения с задержкой и гарантирует, что блок выполняется, если !operation.isFinished — Мэтт: Спасибо за AFNetworking! - person Corey; 07.07.2013

Основываясь на ответах других и предложениях @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.

person Gurpartap Singh    schedule 13.03.2013

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

В файле .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;
}
person Ulaş Sancak    schedule 29.09.2013

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

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

Вы хотите отбросить запрос, когда он становится бездействующим (больше нет передачи) дольше, чем произвольный интервал времени. Пример: вы устанавливаете timeoutInterval на 10 секунд, вы начинаете свой запрос в 12:00:00, он может передавать некоторые данные до 12:00:23, затем время ожидания соединения истекает в 12:00:33. Этот случай охвачен почти всеми ответами здесь (включая JosephH, Mostafa Abdellateef, Cornelius и Gurpartap Singh).

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

Вы хотите удалить запрос, когда он достигает крайнего срока, который происходит произвольно позже. Пример: вы устанавливаете deadline на 10 секунд в будущем, вы начинаете свой запрос в 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()
}
person Cœur    schedule 08.06.2017

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

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

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

person woof    schedule 19.08.2014