我花了工作日的大部分时间来解决这个问题。
背景
我有一个简单的核心数据模型,包括书籍和阅读课程。这些书的封面(图像)以二进制数据形式存储在“允许外部存储”中。
在iOS 11.4及更低版本上,所有时间都可以正常运行。当我保存一个新会话时,所有内容都会正确更新。
问题
从iOS 12开始,当我创建一个新的阅读会话并将其链接到书时,核心数据大约每隔 2秒钟 生成一条SQL语句,该语句也会更新书的封面字段,有时会导致错误引用(对磁盘上的文件的引用)重新启动应用程序时,通常会导致封面 无效 ,并且几乎总是在磁盘上创建封面的重复副本(可以在Simulator的_EXTERNAL_DATA文件夹中看到)。
_EXTERNAL_DATA
内存中的上下文和对象仍然保持正确(因此UI中的所有内容都可以),直到重新启动应用程序,然后封面通常为 nil 。
iOS 12专用
在iOS 12上,我可以确定地在物理设备上的模拟器中重现该错误,并且用户也已报告了该错误。我无法在iOS 11.4上重现该错误,并且没有用户报告iOS 12之前的错误。
采取的步骤
我启用了“ -com.apple.CoreData.ConcurrencyDebug 1”,因此不应该是我从错误的队列访问了任何内容。我还启用了“ -com.apple.CoreData.SQLDebug 3”,这样我就可以准确看到写入的内容。
-com.apple.CoreData.ConcurrencyDebug 1
-com.apple.CoreData.SQLDebug 3
我做了保证书的情况下(因此罩)不是由我的代码通过检查与新会话的关联,然后修改hasChanges,就在我做的newSession.book = book和context.save()。
hasChanges
newSession.book = book
context.save()
为了100%确定我没有触及任何线程上的cover属性,我已将该属性的吸气剂和吸气剂短路了。没提升。
我尝试objectID过在关联并保存之前请求该书的实例。没提升。
objectID
我什至尝试了使用上下文对所有对象保持强引用的选项,以确保这不是某种内存管理问题。没提升。
题
对下一步有什么想法吗?
状态更新
这是iOS 12中的一个缺陷。有关合理的解决方法的详细说明,请参见下面可接受的答案。
更新: 潜在的核心数据问题似乎已在iOS 12.1(已在 beta 4中 验证)中得到解决。我们将在我们的应用程序中保留以下所述的解决方法,并且不建议您很快使用 外部存储 选项。
在与Apple工程师交谈并提交了上述Radar之后,我们迫不及待地想修复该问题,因此我们选择了解决方案,然后切换到将文件存储在文件系统上并直接对其进行自行管理。
我们考虑过的另一种选择是迁移模型,以不允许BLOB使用外部存储,但是我不知道这会对性能产生什么影响,并且我也担心在iOS的这一部分似乎无法迁移模型时不稳定,尤其是在阅读了过去这样的故事之后:核心数据:不要将大文件存储为二进制数据– Alexander Edge –中
自己实施本地存储并没有太大的麻烦。您只需要为每个记录创建一个唯一标识符即可用于创建文件名,以便可以将文件映射到记录。我们向托管对象子类添加了扩展,其中包括用于读取,写入和删除文件的方法。现在,代替调用eg article.photo =image.pngData(),我们现在需要调用类似的东西article.savePhoto(image.pngData()),然后在要检索图像时执行类似的操作。您还可以向这些方法添加一些代码,以支持与当前存储在Core Data中的任何图像的向后兼容性。
article.photo =image.pngData()
article.savePhoto(image.pngData())
删除起来比较棘手,因为我们的对象是从代码中的多个位置删除的,包括级联删除。最后,我选择使用托管对象的prepareForDeletion方法来执行此操作,但这并不理想。这里有很多关于如何最好地实现这一点的讨论:可可-删除 未保存的 核心数据对象时如何处理外部数据的清理?- 堆栈溢出
prepareForDeletion
最后,为防止当非可选二进制属性由于此错误而消失时,我们的应用崩溃,我awakeFromFetch在托管对象子类中进行了重写,以确保所有必需属性都不为零,如果是,则将其设置为占位符图像这样就可以保存它们而不会导致验证失败。
awakeFromFetch