iOS12 具体问题:Core Data External Storage Binary Data corruption
iOS 12 specific problem: Core Data External Storage Binary Data corruption
我花了大半天的时间来解决这个问题。
背景
我有一个简单的核心数据模型,有书籍和阅读课程。这些书的封面(图像)以 "Allows External Storage".
的形式存储为二进制数据
在 iOS 11.4 及以下版本中,一切正常。当我保存新会话时,所有内容都会正确更新。
问题
自从iOS12,当我创建一个新的阅读会话link它到这本书时,大约每隔秒时间,核心数据生成一个SQL 语句也会更新书籍封面字段,有时会导致错误引用(对磁盘上的文件),这通常会导致在重新启动应用程序时封面为 nil,并且几乎总是在磁盘上创建封面的副本(如模拟器的 _EXTERNAL_DATA
文件夹中所示)。
内存上下文和对象保持正确(因此 UI 中的所有内容都正常),直到应用程序重新启动,然后封面通常是 nil.
iOS 12 具体
在 iOS 12 日,我可以确定性地在模拟器、物理设备上重现错误,并且用户也报告了错误。我无法在 iOS 11.4 上重现错误,并且没有用户报告 iOS 12.
之前的错误
已走步数
我已经启用了“-com.apple.CoreData.ConcurrencyDebug 1
”,所以我应该不会访问错误队列中的任何内容。我还启用了“-com.apple.CoreData.SQLDebug 3
”,这样我就可以准确地看到写了什么。
我通过检查 hasChanges
确保 Book 实例(以及封面)在与新会话关联之前没有被我的代码修改,就在我做 newSession.book = book
和 context.save()
.
为了 100% 确定我没有在任何线程上触及封面 属性 我已经为那个 属性 短路了我的 getter 和 setter。没有改善。
我试过使用 objectID
在关联之前请求图书的实例并保存。没有改善。
我什至尝试了上下文保持对所有对象的强引用的选项,只是为了确保它不是某种内存管理问题。没有改善。
问题
对后续步骤有什么想法吗?
状态更新
这是 iOS 12 中的一个缺陷。请参阅下面已接受的答案以获取对合理解决方法的详细描述。
更新: 基础核心数据问题似乎已在 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
以确保任何必需的属性不为零,并且如果它们是,我将它们设置为占位符图像,以便在验证失败的情况下保存它们。
我花了大半天的时间来解决这个问题。
背景
我有一个简单的核心数据模型,有书籍和阅读课程。这些书的封面(图像)以 "Allows External Storage".
的形式存储为二进制数据在 iOS 11.4 及以下版本中,一切正常。当我保存新会话时,所有内容都会正确更新。
问题
自从iOS12,当我创建一个新的阅读会话link它到这本书时,大约每隔秒时间,核心数据生成一个SQL 语句也会更新书籍封面字段,有时会导致错误引用(对磁盘上的文件),这通常会导致在重新启动应用程序时封面为 nil,并且几乎总是在磁盘上创建封面的副本(如模拟器的 _EXTERNAL_DATA
文件夹中所示)。
内存上下文和对象保持正确(因此 UI 中的所有内容都正常),直到应用程序重新启动,然后封面通常是 nil.
iOS 12 具体
在 iOS 12 日,我可以确定性地在模拟器、物理设备上重现错误,并且用户也报告了错误。我无法在 iOS 11.4 上重现错误,并且没有用户报告 iOS 12.
之前的错误已走步数
我已经启用了“
-com.apple.CoreData.ConcurrencyDebug 1
”,所以我应该不会访问错误队列中的任何内容。我还启用了“-com.apple.CoreData.SQLDebug 3
”,这样我就可以准确地看到写了什么。我通过检查
hasChanges
确保 Book 实例(以及封面)在与新会话关联之前没有被我的代码修改,就在我做newSession.book = book
和context.save()
.为了 100% 确定我没有在任何线程上触及封面 属性 我已经为那个 属性 短路了我的 getter 和 setter。没有改善。
我试过使用
objectID
在关联之前请求图书的实例并保存。没有改善。我什至尝试了上下文保持对所有对象的强引用的选项,只是为了确保它不是某种内存管理问题。没有改善。
问题
对后续步骤有什么想法吗?
状态更新
这是 iOS 12 中的一个缺陷。请参阅下面已接受的答案以获取对合理解决方法的详细描述。
更新: 基础核心数据问题似乎已在 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
以确保任何必需的属性不为零,并且如果它们是,我将它们设置为占位符图像,以便在验证失败的情况下保存它们。