简体   繁体   English

Core数据并发如何在Swift 3中使用多线程?

[英]How does Core data concurrency work with multithreading in Swift 3?

In my program, it uses both of 在我的程序中,它使用两者

DispatchQueue.global(qos: .background)

and

self.concurrentQueue.sync(flags: .barrier)

to deal with the background multithread issues. 处理后台多线程问题。

It is swift 3 so I use the latest way to get the childContext: 它是迅速3所以我使用最新的方式来获取childContext:

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

I also enable -com.apple.CoreData.ConcurrencyDebug 1 to debug 我还启用-com.apple.CoreData.ConcurrencyDebug 1进行调试

Then the problem occurs: 然后出现问题:

1, When there's an API call and in the callback block (background thread), I need to fetch the core data, edit, then save. 1,当有API调用并且在回调块(后台线程)中时,我需要获取核心数据,编辑,然后保存。 I tried to use self.context from the code above to call performBlockAndWait and do save inside of this block. 我尝试使用上面代码中的self.context来调用performBlockAndWait并在此块中save The whole process goes fine but when I try to access my result outside of this block but inside of the callback block, the error occurs. 整个过程很顺利但是当我尝试在此块之外但在回调块内部访问我的结果时,会发生错误。 I have also tried to get the objectId and getObjectById by both self.context and self.context.parent and the error occurs on this line. 我还尝试通过self.context和self.context.parent获取objectId和getObjectById ,并且此行发生错误。 What did I do wrong and how should I do this? 我做错了什么,我应该怎么做? since I need to use the result everywhere in many different thread (not context). 因为我需要在许多不同的线程(不是上下文)中的任何地方使用结果。

2, I read a post says that I need one context per thread, then in my case, how do I determine which exact thread it is if it's a call back from API call and do I really need to do this? 2,我读了一篇文章说每个线程需要一个上下文,那么在我的情况下,如果它是从API调用回调的话我如何确定它是哪个确切的线程?我真的需要这样做吗?

3, You might ask that why do I need a privateConcurrentType, because my program has things need to be running in background thread so that it has to do it this way, (read from other post), is this right? 3,你可能会问为什么我需要一个privateConcurrentType,因为我的程序需要在后台线程中运行,所以它必须这样做,(从其他帖子中读取),这是对的吗?

4, Even in my question 1, get object by passing objectId to different Context still not working in my case. 4,即使在我的问题1中,通过将objectId传递给不同的Context来获取对象仍然不能在我的情况下工作。 Let's assume this is the proper way. 我们假设这是正确的方法。 How am I gonna manage passing so many objectID throughout my entire program in different thread without being super messy? 我怎么能管理在整个程序中在不同的线程中传递这么多的objectID而不是超级凌乱? To me this sounds crazy but I suppose there's a much cleaner and easier way to deal with this. 对我来说这听起来很疯狂,但我想有一个更清洁,更简单的方法来解决这个问题。

5, I have read many posts some are pretty old (before swift 3), they have to do childContext.save then parentContext.save , but since I use the code above (swift 3 only). 5,我看过很多帖子有些很旧(在swift 3之前),他们必须做childContext.save然后是parentContext.save ,但是因为我使用上面的代码(仅限swift 3)。 It seems that I can do childContext.save only to make it work? 似乎我可以做childContext.save只是为了让它工作? Am I right? 我对吗?

Core data in general is not multithreading friendly. 核心数据一般不是多线程友好的。 To use it on concurrent thread I can assume only bad things will happen. 要在并发线程上使用它我可以假设只会发生坏事。 You may not simply manipulate managed objects outside the thread on which the context is. 您可能不会简单地操作上下文所在线程之外的托管对象。

As you already mentioned you need a separate context per thread which will work in most cases but by my experience you only need one background context which is read-write and a single main thread read-only context that is used for fetch result controllers or other instant fetches. 正如您已经提到的,每个线程需要一个单独的上下文,在大多数情况下都可以使用,但根据我的经验,您只需要一个读写的后台上下文和一个用于获取结果控制器或其他的主线程只读上下文即时提取。

Think of a context as some in-memory module that communicates with the database (a file). 将上下文视为与数据库(文件)通信的内存模块。 Fetched entities are shared within the context but are not shared between contexts. 提取的实体在上下文中共享,但不在上下文之间共享。 So you can modify pretty much anything inside the context but that will not show in the database or other contexts until you save the context into the database. 因此,您可以修改上下文中的任何内容,但在将上下文保存到数据库之前,这些内容不会显示在数据库或其他上下文中。 And if you modify the same entity on 2 contexts and then save them you will get a conflict which should be resolved by you. 如果您在2个上下文中修改相同的实体然后保存它们,您将遇到应该由您解决的冲突。

All of these then make quite a mess in the code logic and so multiple contexts seem like something to avoid. 所有这些都使代码逻辑变得非常混乱,因此多个上下文似乎是要避免的东西。 What I do is create a background context and then do all of the operations on that context. 我所做的是创建一个后台上下文,然后对该上下文执行所有操作。 Context has a method perform which will execute the code on its own thread which is not main (for background context) and this thread is serial. Context有一个方法perform ,它将在自己的线程上执行代码,该线程不是主要的(对于后台上下文),并且该线程是串行的。

So for instance when doing a smart client I will get a response from server with new entries. 因此,例如在进行智能客户端时,我将从服务器获得具有新条目的响应。 These are parsed on the fly and I perform a block on context to get all the corresponding objects in the database and create the ones that do not exist. 这些都是动态解析的,我在上下文中执行一个块来获取数据库中的所有相应对象并创建不存在的对象。 Then copy the data and save the context into database. 然后复制数据并将上下文保存到数据库中。

For the UI part I do similar. 对于UI部分,我做了类似的事情。 Once an entry should be saved I either create or update the entity on the background context thread. 一旦保存了一个条目,我就可以在后台上下文线程上创建或更新该实体。 Then usually do some UI stuff on completion so I have a method: 然后通常在完成时做一些UI的东西,所以我有一个方法:

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

So pretty much all of the core data logic happens on a single thread which is in background. 所以几乎所有核心​​数据逻辑都发生在后台的单个线程上。 For some cases I do use a main context to get items from fetch result controller for instance; 在某些情况下,我确实使用主上下文来获取来自获取结果控制器的项目; I display a list of objects with it and once user selects one of the items I refetch that item from the background context and use that one in the user interface and to modify it. 我用它显示一个对象列表,一旦用户选择了我从后台上下文中重新获取该项目的项目之一,并在用户界面中使用该项目并进行修改。

But even that may give you trouble as some properties may be loaded lazily from database so you must ensure that all the data you need will be loaded on the context and you may access them on the main thread. 但即便如此,也可能会给您带来麻烦,因为某些属性可能会从数据库中延迟加载,因此您必须确保所需的所有数据都将加载到上下文中,您可以在主线程上访问它们。 There is method for that but I rather use wrappers: 有方法,但我宁愿使用包装器:

I have a single superclass for all the entities in the database model which include id only. 我有一个超类,用于数据库模型中的所有实体,仅包含id So I also have a superclass wrapper which has all the logic to work with the rest of wrappers. 所以我也有一个超类包装器,它具有与其余包装器一起工作的所有逻辑。 What I am left with in the end is that for each of the subclass I need to override 2 mapping methods (from and to) managed object. 我最后留下的是,对于每个子类,我需要覆盖2个映射方法(从和到)托管对象。

It might seem silly to create additional wrappers and to copy the data into memory from managed object but the thing is you need to do that for most of the managed objects anyway; 创建额外的包装器并将数据从托管对象复制到内存中似乎很愚蠢,但事实上你需要对大多数托管对象执行此操作; Converting NSData to/from UIImage , NSDate to/from Date , enumerations to/from integers or strings... So in the end you are more or less just left with strings that are copied 1-to-1. NSData转换为/从UIImage转换, NSDate为/从Date ,枚举转换为整数或字符串......所以最后你或多或少只剩下一对一复制的字符串。 Also this makes it easy to have the code that maps the response from your server in this class or any additional logic where you will have no naming conflicts with managed objects. 此外,这使得在此类或任何其他逻辑中映射来自服务器的响应的代码变得很容易,在这些逻辑中,您将不会与托管对象发生命名冲突。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM