NSRunLoop在iOS10下冻结iPad Pro上的应用程序

时间:2016-11-04 12:28:51

标签: objective-c ios10 nsrunloop

我在自定义操作中得到了一个同步请求,如下所示:

NSURLSessionConfiguration *sessionConfig = [NSURLSessionConfiguration defaultSessionConfiguration];
NSURLSession* session = [NSURLSession sessionWithConfiguration:sessionConfig delegate:self delegateQueue:nil];
NSURLSessionDataTask* task = [session dataTaskWithRequest:request completionHandler:^(NSData *_data, NSURLResponse *_response, NSError *_error)
                              {
                                  self->data = [_data retain];
                                  self->tempResponse = _response;
                                  self->tempError = [_error retain];
                                  self->done = true;
                              }];

[task resume];

// wait until the connection has finished downloading the data or the operation gets cancelled
while (!self->done && !self.isCancelled)
{
    [[NSRunLoop currentRunLoop] run];
}

这段代码非常陈旧,但已经通过几个iOS版本(在某些时候只替换了NSURLConnection)。现在,在iPad Pro的iOS 10下,此代码将冻结我的应用程序。如果我将应用程序放入后台并重新打开它,它将再次运行。此外,如果我在[task resume]self->data = [_data retain];上设置断点,则根本不会发生冻结。

通过在运行循环中添加NSLog,我发现了一种在代码中修复它的方法:

while (!self->done && !self.isCancelled)
{
    NSLog(@"BLAH!");
    [[NSRunLoop currentRunLoop] run];
}

由于所有NSLog都在发布配置中被删除,因此从长远来看,这会有很多性能并且不会有所帮助。

所以我需要一种方法来修复这个bug。也许代码太旧了,而且还有新的方法,我不知道。任何帮助表示赞赏。

1 个答案:

答案 0 :(得分:0)

-run方法将永远存在,直到从运行循环中删除所有计时器等。文档说明:

  

从运行循环中手动删除所有已知输入源和计时器   不保证运行循环将退出。 macOS可以安装和   根据需要删除其他输入源以处理目标请求   在接收者的线程。因此,这些来源可以阻止这些   从退出中运行循环。

因此,iOS 10可能会添加另一个输入源,只有在设备背景化时才会删除,或类似的东西。或者,-run方法可能总是在之前立即返回,如果没有定时器或源(但这将是一个繁忙的等待使用大量的CPU)。你真的无法控制使用这种方法,可能还有其他一些方法。

我在测试用例代码中使用了以下NSRunLoop类别方法,但我不确定我是否会将其推荐用于生产用途,因为它会大量使用CPU,但可能会更改以检查每个.1秒(或更长时间)会有所帮助,而不是使用[NSDate date],这是一个0区间。

- (BOOL)runWithTimeout:(NSTimeInterval)timeout untilCondition:(BOOL (^)(void))testBlock
{
    NSDate *limitDate = [NSDate dateWithTimeIntervalSinceNow:timeout];

    while (1)
    {
        if (testBlock())
            return YES;
        if ([NSDate timeIntervalSinceReferenceDate] > [limitDate timeIntervalSinceReferenceDate])
            return NO;
        if (![self runMode:NSDefaultRunLoopMode beforeDate:[NSDate date]])
            return NO;
    }
}

使用NSURLSession进行同步调用的更常用方法(在子线程中,我假设)是使用信号量:

dispatch_semaphore_t sem = dispatch_semaphore_create(0);  
NSURLSessionDataTask* task = [session dataTaskWithRequest:request completionHandler:^(NSData *_data, NSURLResponse *_response, NSError *_error)
                          {
                              self->data = [_data retain];
                              self->tempResponse = _response;
                              self->tempError = [_error retain];
                              dispatch_semaphore_signal(sem);  
                          }];

[task resume];
dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);  

由于还需要检查取消,这可能会很复杂 - 但您可以使信号量成为实例变量,并覆盖-cancel方法以调用信号量。确保您不再使用该信号量实例,因为您可能有多个信号(并且您可能需要检查网络完成块中的self.isCancelled,以确保您以前不做任何其他工作取消)。