出于某种原因,同时使用 dispatch_async 和 Core Data 会导致我的应用程序完全冻结,但没有崩溃。
症状
用户界面没有响应。该应用程序不会崩溃。
该应用程序用于[[UIAccelermeter sharedAccelerometer] setDelegate: self]
连续获取加速度计值。" accelerometer:didAccelerate:
" 回调在主线程上运行。发生此问题时,回调不再执行。
因此主线程似乎被“冻结”了。
语境
我NSURLConnection
在我的应用程序中使用将 HTTP 请求发送到我的服务器。在处理响应数据的 connectionDidFinishLoading 中,执行大约需要 2 秒,并且在这 2 秒内用户界面将无响应,因为它在主线程中运行。
经过一番研究,在这种情况下执行数据处理似乎gcd/dispatch_async
是最好的解决方案。因此,在 中connectionDidFinishLoading
,我添加了以下 gcd 调用:
dispatch_queue_t queue;
queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(queue, ^{
[self handleTheData: connection];
});
这有时可以正常工作,但它会导致应用程序随机冻结 - 即使使用相同的代码,在 Core Data 存储中使用相同的数据,重复测试表明可以在 X% 的时间内观察到症状。发生问题的可能性受几个因素的影响,下文将进一步解释。
handleTheData() 有什么作用?
它检查来自 的响应数据NSURLConnection
,这是 JSON 格式的字符串。该字符串是 3 个实体列表,每个实体是一个数字数组的数组。因此有代码可以将 JSON 格式的字符串转换为 3 个自定义对象的 NSMutableArray,其中每个对象都是一个子类 NSManagedObject。
在转换过程中,由于这些对象是“临时的”,它们可能会或可能不会永久存储,因此它们是在“nil”NSManagedObjectContext 中创建的,如下所示:
SampleEntity *newEntity=
(SampleEntity *) [[NSManagedObject alloc] initWithEntity:entity
insertIntoManagedObjectContext:nil
];
这样,它们就不会与主要上下文混合。
然后将这些新对象与数据存储中的现有实体进行比较。因此,需要从数据存储中重复获取一些代码以查看特定的新实体是否已经存在,并且根据新实体的数据,新实体可能会保存到数据存储中,或者可能会更新现有实体。
经常调用 [managedObjectContext save: &error] 以确保永久保存更改。
还有什么在后台运行吗?
如症状部分所述,Accelerometer 使用 [[UIAccelermeter sharedAccelerometer] setDelegate: self] 在主线程的后台运行
AudioUnits 也被设置为在后台运行(根据 Apple 文档,在单独的“优先级”线程上)以产生声音。
问题原因的调查结果
发现问题发生的几率受以下因素影响:
- 如果某些调试代码被注释掉,例如 NSLog(@"fetchedResult: %@", fetchedResult),问题发生的机会就会降低(但仍然会发生)。
- 如果不使用加速度计,这个问题几乎不会发生(但不是 100% 肯定,因为我做了有限数量的测试)。
- 如果使用加速度计,问题发生的可能性约为 50/50。
- 如果某些核心数据代码(例如 [sampleEntity setXYZ..])被注释掉,即使使用了加速度计,该问题也几乎不会发生。
第二期
此外,即使主线程没有被冻结,也会发生另一个问题。数据处理代码将在中途结束而不会引发错误。
- 这意味着,数据处理只会部分完成,不会抛出错误,主线程继续正常运行。
- 只要主要问题没有发生,第二个问题就会经常(但并非总是)发生。
有趣的问题不是吗?:)
编辑
- 2013 年 3 月 1 日 - 我之前认为问题的原因是 iOS 6。抱歉,这是完全错误的。修改描述以反映新发现。