【问题标题】:How to set a timeout with AFNetworking如何使用 AFNetworking 设置超时
【发布时间】:2012-01-08 10:14:24
【问题描述】:

我的项目正在使用 AFNetworking。

https://github.com/AFNetworking/AFNetworking

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

【问题讨论】:

  • 我强烈建议不要尝试覆盖超时间隔的任何解决方案,尤其是那些使用performSelector:afterDelay:... 手动取消现有操作的解决方案。详情请看我的回答。

标签: objective-c ios xcode networking afnetworking


【解决方案1】:

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

AFHTTPClient 已经有一个内置机制,可以在 Internet 连接中断时通知您,-setReachabilityStatusChangeBlock:

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


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

  • 甚至可以在开始之前取消请求。将请求排队并不能保证它实际开始的时间。
  • 超时间隔不应取消长时间运行的请求,尤其是 POST。想象一下,如果您尝试下载或上传 100MB 的视频。如果请求在速度较慢的 3G 网络上尽可能地进行,那么如果花费的时间比预期的要长,为什么还要停止它呢?
  • 在多线程应用程序中执行performSelector:afterDelay:... 可能很危险。这会让自己面临晦涩难懂且难以调试的竞争条件。

【讨论】:

  • 我相信这是因为问题是,尽管它的标题,关于如何在“没有互联网”的情况下尽快失败。正确的方法是使用可达性。使用超时要么不起作用,要么很有可能引入难以注意到/修复的错误。
  • @mattt 虽然我同意你的方法,但我正在努力解决我真正需要超时功能的连接问题。问题是 - 我正在与一个暴露“WiFi热点”的设备进行交互。当连接到这个“WiFi网络”时,就可达性而言,虽然我能够对设备执行请求,但我没有连接。另一方面,我确实希望能够确定设备本身是否可访问。有什么想法吗?谢谢
  • 优点,但没有回答有关如何设置超时的问题
  • 在 AFNetworking Reference 中,“网络可达性是一种诊断工具,可用于了解请求可能失败的原因。它不应用于确定是否发出请求。”在“setReachabilityStatusChangeBlock”部分。所以我认为'setReachabilityStatusChangeBlock'不是解决方案......
  • 我理解为什么我们应该三思而后行手动设置超时,但是这个接受的答案并没有回答这个问题。当互联网连接丢失时,我的应用程序需要尽快知道。使用 AFNetworking,ReachabilityManager 不会检测到设备连接到 Wifi 热点的情况,但热点本身会丢失互联网(通常需要几分钟才能检测到)。因此,在这种情况下,以约 5 秒的超时时间向 Google 发出请求似乎是我最好的选择。除非我错过了什么?
【解决方案2】:

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

但是,如果您仍然想设置超时(没有performSelector:afterDelay: 等固有的所有问题,那么乐高提到的拉取请求描述了一种作为 cmets 之一的方法,您只需这样做:

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 文档说:

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

【讨论】:

  • 是的,我试过了,在做 POST 请求时它不起作用。
  • 旁注: Apple 在 iOS 6 中修复了这个问题
  • 这不符合您的建议。 timeoutInterval 是一个空闲计时器,而不是请求超时。因此,您必须在 120 秒内完全没有收到任何数据,上述代码才会超时。如果数据慢慢流入,请求可能会无限期地继续。
  • 这是一个公平的观点,我并没有真正过多地谈论它是如何工作的 - 我希望现在已经添加了这些信息,谢谢!
【解决方案3】:

您可以通过 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
    }];

【讨论】:

    【解决方案4】:

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

    我正在继承 AFHTTPClient 并更改了

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

    加法

    [request setTimeoutInterval:10.0];
    

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

    【讨论】:

    • 要考虑的另一件事是 Apple 会覆盖 POST 的超时。我认为自动大约 4 分钟,你不能改变它。
    • 我也看到了。为什么会这样?在连接失败前让用户等待 4 分钟是不好的。
    • 添加到@KCHARwood 响应。从 iOS 6 开始,苹果不会覆盖帖子的超时。这已在 iOS 6 中修复。
    【解决方案5】:

    终于知道了如何使用异步 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];。而是使用与异步请求相同的方法,并等待您在选择器参数中传递的函数被触发。

    【讨论】:

    • 不不不不不,不要在实际应用中使用它。您的代码实际上所做的是在在创建操作时开始的时间间隔后取消请求,不是在它开始时。这可能会导致请求在开始之前就被取消。
    • @mattt 请提供一个有效的代码示例。实际上,您所描述的正是我想要发生的事情:我希望时间间隔在我创建操作的那一刻开始计时。
    • 感谢乐高!我正在使用 AFNetworking,并且在大约 10% 的时间里,我从 AFNetworking 进行的操作从不调用成功或失败块[服务器在美国,测试用户在中国]。这对我来说是一个大问题,因为我故意在这些请求正在进行时阻止部分 UI,以防止用户一次发送太多请求。我最终实现了一个基于此解决方案的版本,它通过 performselector withdelay 将完成块作为参数传递,并确保在 !operation.isFinished 时执行该块 - Mattt:感谢 AFNetworking!
    【解决方案6】:

    根据其他人的回答和@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 上运行。

    【讨论】:

      【解决方案7】:

      我们不能用这样的计时器来做吗:

      在.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;
      }
      

      【讨论】:

        【解决方案8】:

        这里的“超时”定义有两种不同的含义。

        超时如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()
        }
        

        【讨论】:

          【解决方案9】:

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

          如苹果文档所述:

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

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 2013-11-26
            • 2019-04-05
            • 1970-01-01
            • 1970-01-01
            • 2011-07-02
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多