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