如何使用AFNetworking设置超时


79

我的项目正在使用AFNetworking。

https://github.com/AFNetworking/AFNetworking

如何降低超时时间?没有互联网连接的自动取款机大约2分钟之内不会触发故障阻止。漫长的...


2
我强烈建议您不要尝试使用任何超时超时解决方案,尤其是performSelector:afterDelay:...用于手动取消现有操作的解决方案。请参阅我的答案以获取更多详细信息。
mattt 2012年

Answers:


110

更改超时间隔几乎绝对不是解决您所描述问题的最佳解决方案。相反,似乎您真正想要的是让HTTP客户端处理无法访问的网络,不是吗?

AFHTTPClient已经具有内置机制,可以在互联网连接断开时通知您-setReachabilityStatusChangeBlock:

在慢速的网络上,请求可能会花费很长时间。最好让iOS知道如何处理慢速连接,并告诉它与根本没有连接之间的区别。


为了进一步说明为什么应避免使用此线程中提到的其他方法的原因,这里有一些想法:

  • 甚至可以在开始之前取消请求。使请求入队并不能保证它真正开始的时间。
  • 超时间隔不应取消长时间运行的请求,尤其是POST。想象一下,如果您尝试下载或上传100MB的视频。如果请求在慢速3G网络上能尽力而为,那么如果花费的时间比预期的长一点,您为什么会不必要地停止它呢?
  • performSelector:afterDelay:...在多线程应用程序中这样做可能很危险。这为模糊和难以调试的比赛条件敞开了自己的大门。

我相信这是因为问题(尽管有标题)是关于在“没有互联网”的情况下如何尽快失败。正确的方法是使用可达性。使用超时要么无效,要么很有可能引入难以发现/修复的错误。
Mihai Timar 2013年

2
@mattt尽管我确实同意您的方法,但是我在确实需要超时功能的连接问题上苦苦挣扎。问题是-我正在与一个暴露“ WiFi热点”的设备进行交互。当连接到此“ WiFi网络”时,尽管可以执行对设备的请求,但仍未连接到我。另一方面,我确实希望能够确定设备本身是否可访问。有什么想法吗?谢谢
Stavash13年

42
好点,但没有回答有关如何设置超时的问题
Max MacLeod

3
在AFNetworking参考上,“网络可达性是一种诊断工具,可用于了解请求失败的原因。不应将其用于确定是否发出请求。” 在“ setReachabilityStatusChangeBlock”部分。所以我认为'setReachabilityStatusChangeBlock'不是解决方案……
LKM

1
我知道为什么我们应该对手动设置超时进行三思,但是这个公认的答案并不能回答问题。当互联网连接断开时,我的应用需要尽快知道。使用AFNetworking,ReachabilityManager不会检测到设备连接到Wifi热点的情况,但是该热点本身会失去Internet连接(通常需要几分钟才能检测到)。因此,在这种情况下,最好以大约5秒的超时时间向Google发送请求。除非我缺少什么?
Tanner Semerad

44

我强烈建议您查看上面的mattt答案-尽管此答案不会与他通常提到的问题相抵触,但对于原始发帖人问题,检查可达性更合适。

但是,如果您仍然想设置一个超时时间(而没有performSelector:afterDelay:etc中固有的所有问题,那么Lego的pull request提述方法将其描述为一种注释方式),您可以这样做:

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秒。


是的,我尝试了它,并且在执行POST请求时不起作用。
borisdiakur'1

7
旁注: Apple在iOS 6
Raptor

2
这并没有按照您的建议执行。timeoutInterval是一个空闲计时器,不是请求超时。因此,您必须在120秒内完全不接收任何数据,以上代码才能超时。如果数据缓慢滴入,则请求可能会无限期地继续。
Christopher Pickslay

公平的一点是,我并没有对它的工作原理做过多说明-希望我现在已经添加了此信息,谢谢!
JosephH


26

您可以通过requestSerializer setTimeoutInterval方法设置超时间隔。可以从AFHTTPRequestOperationManager实例获取requestSerializer。

例如,发出超时为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];

AFHTTPClient.m第236行中。如果可以进行配置,那当然会很好,但是据我所知,目前无法实现。


7
要考虑的另一件事是,Apple覆盖了POST的超时。我认为大约4分钟会自动完成,您无法更改。
kcharwood 2011年

我也看到了。为什么会这样呢?让用户在连接失败之前等待4分钟是不好的。
slatvick 2012年

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
否否否否否,不要在实际应用中使用此功能。你的代码实际上做的是取消时间间隔之后请求时创建操作启动,不是当它开始。这可能导致请求甚至在开始之前就被取消。
mattt 2012年

5
@mattt请提供一个有效的代码示例。实际上,您所描述的正是我想要发生的事情:我希望时间间隔在创建操作的那一刻开始计时。
borisdiakur'4

谢谢乐高!我使用的是AFNetworking,并且在我从AFNetworking进行的操作中大约有10%的时间从未调用成功或失败块(服务器在美国,测试用户在中国)。这对我来说是个大问题,因为在这些请求进行过程中我故意阻止了UI的各个部分,以防止用户一次发送太多请求。我最终实现了基于此解决方案的版本,该版本通过performselector延迟传递一个完成块作为参数,并确保如果!operation.isFinished执行该块-Mattt:谢谢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

您希望在空闲(不再传输)的时间超过任意时间间隔时丢弃请求。例如:您设置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之前超时。此案由borisdiakur负责。

我想展示如何在AFNetworking 3.1的Swift(第3和第4版)中实现此截止日期

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()
}

并给出一个可测试的示例,此代码应打印“ failure”(失败)而不是“ success”(成功),因为将来立即超时为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()
}

-2

同意Matt的意见,您不应该尝试更改timeoutInterval。但是,您也不应依靠可达性检查来确定要建立连接的天气,直到尝试时才知道。

如Apple文件所述:

通常,您不应使用短的超时间隔,而应为用户提供一种取消长时间运行的操作的简便方法。有关更多信息,请阅读“为真实网络设计”。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.