iOS:NSURLConnection回调严重延迟或根本没有被触发

时间:2014-10-01 12:32:59

标签: ios background nsurlconnection voip

我使用长轮询机制获得了一个VoIP iOS应用程序来维护其服务连接并接收事件(调用)。这意味着,NSURLConnection等待几分钟,但会在事件发生后立即返回。由于VoIP标志,即使应用程序处于后台模式,也可以设置保持活动处理程序并接收更新。

但是,大部分时间但不可靠。有时,即使请求超时(NSURLConnection已达到timeoutInterval),NSURLRequest回调也会严重延迟或根本不会被触发。

日志中的一个例子澄清:

  • 该应用程序以后台模式运行(由系统在启动时启动)
  • NSURLConnection#1(长轮询)启动并在1分钟后返回一些新数据
  • NSURLConnection#2(长轮询)启动并在15分钟后(服务器端最大值)返回,没有任何新数据
  • (...)
  • NSURLConnection #99(长轮询)已启动,但未返回 - 甚至在timeoutInterval到期后(16分钟)
  • 有时会调用keep alive handler,bot什么都不会发生。 backgroundTimeRemaining属性获得了不切实际的高值(179769313486231570814527423731704356798070567525844996598917476803157260780028538760589558632766878171540458953514382464234321326889464182768467546703537516986049910576551282076245490090389328944075868508455133942304583236903222948165808559332123348274797826204144723168738177180919299881250404026184124858368.0,而不是最大值180.0)。
  • 1小时后,用户打开应用程序。该应用能够执行各种NSURLRequest和接收回复
  • 经过一段时间后,用户关闭应用
  • 再过10分钟后,NSURLConnection#99回调didFailWithError因超时错误(-1001)而被解雇。此请求的超时时间超过一小时,即使timeoutInterval被限制为16分钟,还有其他几个请求在稍后启动但稍早完成。

从我的角度来看,这似乎是iOS的一种非常奇怪的行为。 iOS为什么要给应用程序提供后台执行时间并调用keep alive处理程序,但是没有及时正确触发NSURLConnection回调?

保持活着的处理程序:

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {

    [[UIApplication sharedApplication] setKeepAliveTimeout:600 handler:^{

        NSLog(@"########### Started Keep-Alive Handler ###########");

        [self startBackgroundHandler:YES timeout:30];

        NSLog(@"########### Completed Keep-Alive Handler ###########");

    }];

    [self startBackgroundHandler:NO timeout:60];

}

-(void)startBackgroundHandler:(BOOL)force timeout:(int)timeout {
    UIApplicationState currentAppState = [[UIApplication sharedApplication] applicationState];
    BOOL appIsBackground = currentAppState == UIApplicationStateBackground;
    if(appIsBackground || force) {

        int localThreadId = ++_currentBackgroundThreadId;

        __block UIBackgroundTaskIdentifier bgTask;
        bgTask = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{
            // Clean up any unfinished task business by marking where you
            // stopped or ending the task outright.
            NSLog(@"Cleaning up [Background Thread %d] ...", localThreadId);
            [[UIApplication sharedApplication] endBackgroundTask:bgTask];
            bgTask = UIBackgroundTaskInvalid;
        }];

        NSLog(@"startBackgroundHandler with [Background Thread %d] appIsBackground=%d force=%d", localThreadId, appIsBackground, force);

        // Start the long-running task and return immediately.
        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

            if(_currentBackgroundThreadId == localThreadId) {
                NSLog(@"[Background Thread %d] Background time left: %0.1f", localThreadId, [UIApplication
                                                    sharedApplication].backgroundTimeRemaining);
                sleep(timeout);
            }

            NSLog(@"[Background Thread %d] Will exit...", localThreadId);
            [[UIApplication sharedApplication] endBackgroundTask:bgTask];
            bgTask = UIBackgroundTaskInvalid;
        });
    } else {
        NSLog(@"Ignored startBackgroundHandler - appIsBackground=%d force=%d", appIsBackground, force);
    }
}

所有NSURLConnections都有一个runloop - 它们按如下方式启动:

NSURLConnection* connection = [[NSURLConnection alloc] initWithRequest:mrequest delegate:self startImmediately:NO];
if(connection) {
    [connection scheduleInRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];
    [connection start];

} else {
    // error handling...
}

PS: 在应用程序的早期版本中,使用data fetch后台模式而不是voip,并且从未遇到过这种问题。

1 个答案:

答案 0 :(得分:1)

似乎iOS不愿意在你处于后台时给你主线程上的CPU时间 - 即使你有VoIP标志。因此,您应该在单独的线程中安排您的请求,并使用CFRunLoopRun()使其在后台运行。此外,您必须使用runloop上的runMode:beforeDate:触发执行适当的模式。

但真正的问题是,如果请求经常超时,iOS将停止提供CPU时间 - 你真的需要接收任何东西才能获得CPU时间。因此,让WebServer及时回复非常重要。