【问题标题】:Core Data Concurrency issue in Swift 4Swift 4 中的核心数据并发问题
【发布时间】:2018-09-04 03:10:51
【问题描述】:

当线程不是主线程时,我在从核心数据中检索 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中的retrieve函数

    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] 的计数都会增加。此外,当它在主线程中时,一切正常。

有人有什么想法吗?

【问题讨论】:

    标签: ios swift multithreading core-data concurrency


    【解决方案1】:

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

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

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

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

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

    如果您希望只有不到一百个实体在 viewContext 上读取文字,这不是不合理的设置。如果是这种情况,我建议您这样做,因为它可以解决您的大部分问题。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-07-02
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-07-26
      相关资源
      最近更新 更多