80

我的项目正在使用 AFNetworking。

https://github.com/AFNetworking/AFNetworking

如何拨出超时?没有互联网连接的自动柜员机在大约 2 分钟内不会触发失败块。太长了……

4

9 回答 9

110

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

AFHTTPClient已经有一个内置机制让您知道何时失去互联网连接,-setReachabilityStatusChangeBlock:.

在慢速网络上请求可能需要很长时间。最好相信 iOS 知道如何处理慢速连接,并区分这与完全没有连接之间的区别。


为了扩展我关于为什么应该避免在这个线程中提到的其他方法的推理,这里有一些想法:

  • 请求甚至可以在它们开始之前被取消。将请求排队并不能保证它实际开始的时间。
  • 超时间隔不应取消长时间运行的请求——尤其是 POST。想象一下,如果您尝试下载或上传 100MB 的视频。如果请求在缓慢的 3G 网络上尽可能地进行,那么如果它花费的时间比预期的长一点,你为什么要不必要地停止它呢?
  • performSelector:afterDelay:...在多线程应用程序中这样做可能很危险。这使自己面临晦涩难懂且难以调试的竞争条件。
于 2012-04-08T20:22:52.183 回答
44

我强烈建议您查看上面的 mattt 答案——尽管这个答案与他通常提到的问题并不冲突,但对于原始发帖人的问题,检查可达性更合适。

但是,如果您仍然想设置超时(没有performSelector:afterDelay:etc 固有的所有问题,那么乐高提到的拉取请求描述了一种方法作为评论之一,您只需执行以下操作:

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 指出的那样,这不是整体超时,而是接收(或发送数据)之间的超时。我不知道有什么方法可以明智地进行整体超时。setTimeoutInterval 的 Apple 文档说:

超时间隔,以秒为单位。如果在连接尝试期间请求保持空闲的时间超过超时间隔,则认为请求已超时。默认超时间隔为 60 秒。

于 2012-01-03T15:24:54.397 回答
28

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

例如,执行一个超时为 25 秒的 post 请求:

    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
    }];
于 2014-07-16T21:52:04.193 回答
7

我认为你现在必须手动修补它。

我正在继承 AFHTTPClient 并更改了

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

方法通过添加

[request setTimeoutInterval:10.0];

AFHTTPClient.m第 236 行。当然,如果可以配置它会很好,但据我所知,目前这是不可能的。

于 2011-11-29T09:42:23.050 回答
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];. 而是使用与异步请求相同的方法并等待触发您在选择器参数中传递的函数。

于 2012-01-07T23:48:52.797 回答
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 上运行。

于 2013-03-13T20:19:18.943 回答
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;
}
于 2013-09-29T16:12:55.883 回答
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 报道。

我想展示如何在 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()
}
于 2017-06-08T04:48:45.777 回答
-2

同意马特,您不应该尝试更改 timeoutInterval。但是你也不应该依赖可达性检查来决定你要建立连接的天气,直到你尝试才知道。

正如苹果文件所述:

作为一般规则,您不应使用较短的超时间隔,而应为用户提供一种简单的方法来取消长时间运行的操作。有关更多信息,请阅读“为现实世界网络设计”。

于 2014-08-19T00:30:03.457 回答