【问题标题】:NSRunLoop freezes app on iPad Pro under iOS10NSRunLoop 在 iOS10 下冻结 iPad Pro 上的应用
【发布时间】:2016-11-04 12:28:51
【问题描述】:

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

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 都已为发布配置删除。

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

【问题讨论】:

  • 也许 runUntil 提前一小段时间运行,直到有帮助...
  • 我已经尝试了 runUntil: 和 runMode:beforeDate:,但没有任何效果:(
  • 一般来说,使用 dispatch_semaphore 或 dispatch group 可能会更好。

标签: objective-c ios10 nsrunloop


【解决方案1】:

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

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

因此,iOS 10 可能会添加另一个输入源,该输入源仅在设备处于后台或类似情况时才会被删除。或者,如果没有计时器或源,-run 方法可能总是在之前立即返回(但这将是使用大量 CPU 的繁忙等待)。使用这种方法,可能还有其他一些方法,你真的没有太多控制权。

我在测试用例代码中使用了以下 NSRunLoop 类别方法,但我不确定我是否会推荐它用于生产用途,因为它会大量使用 CPU,尽管可能会更改为每 0.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 以确保如果之前取消您不会做任何额外的工作)。

【讨论】:

    猜你喜欢
    • 2017-04-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-03-25
    • 1970-01-01
    • 2022-06-17
    • 1970-01-01
    • 2019-11-24
    相关资源
    最近更新 更多