ios - Swift 4 中的核心数据并发问题

标签 ios swift multithreading core-data concurrency

当线程不是主线程时,从核心数据中检索 NSManagedObj 时遇到核心数据并发问题。

我尝试了 Swift 4 的核心数据,当代码很简单时,一切都运行良好。然而,当我为核心数据添加更多代码并增加复杂性时,我偶尔会出错并发现核心数据的并发问题。

我遇到的错误是

CoreData: error: NULL _cd_rawData but the object is not being turned into a fault

我搜索了一些方法来尝试通过添加以下行来解决涉及后台线程而不是主线程的检索核心数据操作。

appDelegate.persistentContainer.performBackgroundTask{....}

然而,它仍然存在问题。

首先,这里是检索函数

func retrieve() -> MasterSlave?{
    let appDelegate = UIApplication.shared.delegate as! AppDelegate
    let context = appDelegate.persistentContainer.viewContext
    var count = 0
    var masterSlave: MasterSlave?
    DispatchQueue.main.async {
        print("master slave receive main thread is", Thread.isMainThread)
        count = appDelegate.retrieve("MasterSlave", predicate: nil, sort: nil, limit: nil)!.count
        if count > 0 {
            masterSlave = appDelegate.retrieve("MasterSlave", predicate: nil, sort: nil, limit: nil)?.first! as? MasterSlave
        }
    }

    appDelegate.persistentContainer.performBackgroundTask{ context in
        print("master slave receive main thread is", Thread.isMainThread)
        let ms = appDelegate.retrieve("MasterSlave", predicate: nil, sort: nil, limit: nil)!
        print("ms obj retreived is", ms, "count is ", ms.count)
        count = appDelegate.retrieve("MasterSlave", predicate: nil, sort: nil, limit: nil)!.count
        if count > 0 {
            masterSlave = appDelegate.retrieve("MasterSlave", predicate: nil, sort: nil, limit: nil)?.first! as? MasterSlave
        }
    }
    return masterSlave
}

这里是 appDelegate 中的检索函数

    func retrieve(_ myEntityName:String, predicate:String?, sort:[[String:Bool]]?, limit:Int?) -> [NSManagedObject]? {
    let myContext = persistentContainer.viewContext
    let request = NSFetchRequest<NSFetchRequestResult>(entityName: myEntityName)

    // predicate
    if let myPredicate = predicate {
        request.predicate = NSPredicate(format: myPredicate)
    }

    // sort
    if let mySort = sort {
        var sortArr :[NSSortDescriptor] = []
        for sortCond in mySort {
            for (k, v) in sortCond {
                sortArr.append(NSSortDescriptor(key: k, ascending: v))
            }
        }

        request.sortDescriptors = sortArr
    }

    // limit
    if let limitNumber = limit {
        request.fetchLimit = limitNumber
    }


    do {
        return try myContext.fetch(request) as? [NSManagedObject]

    } catch {
        fatalError("\(error)")
    }

    return nil
}

现在,发现了一些奇怪的东西: MasterSlave 实体应该只有一个我之前保存过一次的对象。所以当它运行let ms = appDelegate.retrieve("MasterSlave", predicate: nil, sort: nil, limit: nil)!时,预计会检索到如下数据

data: {
encryptKey = nil;
hasMaster = 0;
lock = 0;
master = 0;
password = nil;
slave = 1;
}

但它给出了以下内容

[<MasterSlave: 0x600000280190> (entity: MasterSlave; id: 0xd000000000040004 <x-coredata://5FF8490C-B1C5-417D-BA3E-2F30B5D2BAD6/MasterSlave/p1> ; data: <fault>), 
 <MasterSlave: 0x600000280140> (entity: MasterSlave; id: 0xd000000000080004 <x-coredata://5FF8490C-B1C5-417D-BA3E-2F30B5D2BAD6/MasterSlave/p2> ; data: <fault>),
 <MasterSlave: 0x60000009fd60> (entity: MasterSlave; id: 0xd0000000000c0004 <x-coredata://5FF8490C-B1C5-417D-BA3E-2F30B5D2BAD6/MasterSlave/p3> ; data: <fault>),
 <MasterSlave: 0x6000002800f0> (entity: MasterSlave; id: 0xd000000000100004 <x-coredata://5FF8490C-B1C5-417D-BA3E-2F30B5D2BAD6/MasterSlave/p4> ; data: <fault>), 
 <MasterSlave: 0x6000002800a0> (entity: MasterSlave; id: 0xd000000000140004 <x-coredata://5FF8490C-B1C5-417D-BA3E-2F30B5D2BAD6/MasterSlave/p5> ; data: <fault>), 
 <MasterSlave: 0x600000280050> (entity: MasterSlave; id: 0xd000000000180004 <x-coredata://5FF8490C-B1C5-417D-BA3E-2F30B5D2BAD6/MasterSlave/p6> ; data: <fault>), 
 <MasterSlave: 0x600000280000> (entity: MasterSlave; id: 0xd0000000001c0004 <x-coredata://5FF8490C-B1C5-417D-BA3E-2F30B5D2BAD6/MasterSlave/p7> ; data: <fault>), 
 <MasterSlave: 0x60000009ff90> (entity: MasterSlave; id: 0xd000000000200004 <x-coredata://5FF8490C-B1C5-417D-BA3E-2F30B5D2BAD6/MasterSlave/p8> ; data: <fault>), 
 <MasterSlave: 0x60000009ff40> (entity: MasterSlave; id: 0xd000000000240004 <x-coredata://5FF8490C-B1C5-417D-BA3E-2F30B5D2BAD6/MasterSlave/p9> ; data: <fault>), 
 <MasterSlave: 0x60000009fef0> (entity: MasterSlave; id: 0xd000000000280004 <x-coredata://5FF8490C-B1C5-417D-BA3E-2F30B5D2BAD6/MasterSlave/p10> ; data: <fault>), 
 <MasterSlave: 0x60000009fea0> (entity: MasterSlave; id: 0xd0000000002c0004 <x-coredata://5FF8490C-B1C5-417D-BA3E-2F30B5D2BAD6/MasterSlave/p11> ; data: <fault>), 
 <MasterSlave: 0x60000009fe50> (entity: MasterSlave; id: 0xd000000000300004 <x-coredata://5FF8490C-B1C5-417D-BA3E-2F30B5D2BAD6/MasterSlave/p12> ; data: <fault>), 
 <MasterSlave: 0x60000009fe00> (entity: MasterSlave; id: 0xd000000000340004 <x-coredata://5FF8490C-B1C5-417D-BA3E-2F30B5D2BAD6/MasterSlave/p13> ; data: <fault>), 
 <MasterSlave: 0x60000009fdb0> (entity: MasterSlave; id: 0xd000000000380004 <x-coredata://5FF8490C-B1C5-417D-BA3E-2F30B5D2BAD6/MasterSlave/p14> ; data: <fault>), 
 <MasterSlave: 0x60000009fcc0> (entity: MasterSlave; id: 0xd0000000003c0004 <x-coredata://5FF8490C-B1C5-417D-BA3E-2F30B5D2BAD6/MasterSlave/p15> ; data: <fault>), 
 <MasterSlave: 0x600000280230> (entity: MasterSlave; id: 0xd000000000400004 <x-coredata://5FF8490C-B1C5-417D-BA3E-2F30B5D2BAD6/MasterSlave/p16> ; data: <fault>), 
 <MasterSlave: 0x60000009fa90> (entity: MasterSlave; id: 0xd000000000440004 <x-coredata://5FF8490C-B1C5-417D-BA3E-2F30B5D2BAD6/MasterSlave/p17> ; data: {
encryptKey = nil;
hasMaster = 0;
lock = 0;
master = 0;
password = nil;
slave = 1;
})]

当我停止应用程序并再次部署时,masterSlave 的 [NSManagedObject] 的计数变为 18 而不是 17。

我不明白为什么每次构建部署时检索到的 [NSManagedObject] 的计数都会增加。此外,当它在主线程中时,一切正常。

有没有人有什么想法?

最佳答案

核心数据不是线程安全的。既不适合阅读,也不适合写作。每个上下文都有一个(且只有一个)可以安全访问的线程。对于 viewContext,它是主线程。对于由 performBackgroundTask 创建的上下文,它应该只能在该 block 内访问。对于上下文和与上下文关联的所有受管对象都是如此。

你的 retrieve 方法,你从核心数据中获取,你应该接受一个上下文作为参数。当它从 main 使用时,应该将它传递给 viewContext;当它来自 performBackgroundTask 时,它应该使用传递给 block 的上下文。

您不能将对象传入或传出 performBackgroundTask block 。因此,您不应从 performBackgroundTask 内部访问任何主线程对象。相反,您应该保存对象的 objectID 并使用它重新获取。您不能将 managedObjects 传递到 block 外,但可以传递其中的数据。

您在 retrieve() 中使用 block 是荒谬的。该方法在任一 block 执行之前返回,masterSlave 将始终为 nil。一般来说,你应该从一个你知道已经在主线程上的方法在主线程上获取。使用 DispatchQueue.main.async 进行获取,然后尝试将该数据发送回后台线程绝不是一个好主意 - 您通常希望在主线程上显示信息.

如果你想保证一个实体只有一个实例,你应该在创建它之前对该对象进行一次提取。看起来每次应用程序启动时您都在创建一个新对象。您尚未共享该代码,因此我无法进一步评论。

如果您希望读取的实体少于大约一百个,那么只在 viewContext 上写入并不是一个不合理的设置。如果是这种情况,我建议您这样做,因为它可以解决您的大部分问题。

关于ios - Swift 4 中的核心数据并发问题,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/49483577/

相关文章:

ios - AVAudioRecorder - 展开可选时发现 nil

c++ - 如何安全终止多线程进程

python - 在python上并行执行和文件写入

ios - 无法使用自定义类返回类型覆盖方法

ios - 跟踪 UIView 中的 TouchUpInside 事件

java - 是否有必要同步线程安全单例的方法

ios - 如何直接在屏幕上显示ios应用程序中的消息,例如铃声/静音显示

ios - UIPageControl 似乎并不总是在滚动时发生变化

ios - 简单 Estimote (iBeacon) Xamarin 示例不工作

ios - Swift 中获取和设置 UITextField 和 UITextView 的光标位置