Core Data在Swift 3中如何与多线程并发工作?

3
在我的程序中,它同时使用了以下两种技术:
DispatchQueue.global(qos: .background)

并且。
self.concurrentQueue.sync(flags: .barrier)

处理后台多线程问题。

由于使用的是 Swift 3,所以我使用了最新的方法来获取子上下文:

lazy var context: NSManagedObjectContext = {
    return (UIApplication.shared.delegate as! AppDelegate).persistentContainer.newBackgroundContext()
}()

我也启用了-com.apple.CoreData.ConcurrencyDebug 1来调试。

然后问题出现了:

1、当有API调用并且在回调块(后台线程)中,我需要获取核心数据,进行编辑,然后保存。我尝试使用上面的代码中的self.context调用performBlockAndWait,并在此块内部执行save。整个过程很顺利,但是当我试图在此块之外但在回调块之内访问我的结果时,就会出现错误。我还尝试通过self.context和self.context.parent获取objectId和getObjectById,但是这行会出现错误。我做错了什么,应该怎么做?因为我需要在许多不同的线程(而不是上下文)中使用结果。

2、我读到一篇文章说我需要每个线程一个上下文,在我的情况下,如果它是API调用的回调,我如何确定确切的线程,我真的需要这样做吗?

3、你可能会问我为什么需要一个privateConcurrentType,因为我的程序有需要在后台线程中运行的东西,所以必须这样做(从其他帖子中阅读),这是正确的吗?

4、即使在我的问题1中,通过将objectId传递给不同的上下文来获取对象仍然无法在我的情况下工作。假设这是正确的方法,我该如何管理在整个程序中在不同线程中传递这么多objectID而不会变得非常混乱?对我来说这听起来很疯狂,但我想应该有更清洁和更简单的方法来处理这个问题。

5、我阅读了许多旧帖子(在swift 3之前),它们必须执行childContext.save然后parentContext.save,但是由于我使用了上面的代码(仅限swift 3)。似乎我只需要执行childContext.save就可以使其工作?我是对的吗?


当您尝试在后台线程中访问上下文时,“错误”究竟是什么? - Scott Thompson
1个回答

9

总体来说,核心数据并不支持多线程。在并发线程上使用它可能会导致意想不到的问题。你不能在上下文之外的线程上简单地操作托管对象。

正如你已经提到的,你需要为每个线程创建一个单独的上下文,在大多数情况下这是可行的,但根据我的经验,你只需要一个后台读写上下文和一个用于获取结果控制器或其他即时获取的主线程只读上下文。

将上下文视为某种与数据库(文件)通信的内存模块。检索的实体在上下文内共享,但在上下文之间不共享。因此,你可以修改上下文中的任何内容,但在保存上下文到数据库之前,这些更改不会显示在数据库或其他上下文中。如果在两个上下文中修改了相同的实体,然后保存它们,你将遇到冲突,需要自行解决。

所有这些都使代码逻辑变得非常混乱,因此尽量避免使用多个上下文。我会创建一个后台上下文,然后在该上下文上执行所有操作。上下文有一个名为perform的方法,它将在其自己的线程上执行代码,该线程不是主线程(对于后台上下文),并且该线程是串行的。

例如,在进行智能客户端时,我会从服务器获取响应以获取新条目。这些条目将实时解析,并在上下文上执行一个块以获取数据库中所有对应的对象并创建不存在的对象。然后将数据复制并保存上下文到数据库中。

对于 UI 部分,我也做类似的事情。一旦需要保存条目,我就在后台上下文线程上创建或更新实体。然后通常在完成后进行一些 UI 处理,因此我有这样一个方法:

    public func performBlockOnBackgroundContextAndReturnOnMain(block: @escaping (() -> Void), main: @escaping (() -> Void)) {
        if let context = context {
            context.perform {
                block()
                DispatchQueue.main.async(execute: { () -> Void in
                    main()
                })
            }
        }
    }

基本上所有的核心数据逻辑都在后台的单个线程上执行。对于某些情况,我会使用主要上下文来获取从提取结果控制器中获取的项目;例如,我使用它显示对象列表,一旦用户选择其中一个项目,我就从后台上下文中重新提取该项目,并在用户界面和修改它时使用该项目。
但即使如此,这也可能会给您带来麻烦,因为某些属性可能会从数据库中懒加载,因此您必须确保您需要的所有数据都将在上下文中加载,并且您可以在主线程上访问它们。有一个方法可以做到这一点,但我更喜欢使用包装器:
我有一个用于数据库模型中所有实体的单个超类,其中仅包括id。因此,我还有一个超类包装器,其中包含与其余包装器一起工作的所有逻辑。最终,对于每个子类,我需要覆盖2个映射方法(从和到托管对象)。
创建附加包装器并从托管对象复制数据到内存可能看起来很愚蠢,但事实是您无论如何都需要这样做大多数托管对象;将NSData转换为/从UIImage,将NSDate转换为/从Date,将枚举转换为/从整数或字符串...因此,最终您基本上只剩下一个被复制的字符串。此外,这使得在此类中映射来自服务器的响应或任何其他逻辑的代码变得容易,您将不会与托管对象发生命名冲突。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接