我在自定义操作中得到了一个同步请求,如下所示:
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。也许代码太旧了,而且还有新的方法,我不知道。任何帮助表示赞赏。
答案 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,以确保您以前不做任何其他工作取消)。