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

时间:2018-03-26 03:37:50

标签: 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]的计数都会递增。此外,当它在主线程中时,事情正常。

有没有人有任何想法?

1 个答案:

答案 0 :(得分:1)

核心数据不是线程安全的。无论是阅读还是写作。每个上下文都有一个(也是唯一一个)可以安全访问的线程。对于viewContext,它是主线程。对于performBackgroundTask创建的上下文,只应在该块内访问它。对于上下文和与上下文关联的所有托管对象都是如此。

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

您无法将对象传入或传出performBackgroundTask块。所以你不应该从performBackgroundTask中访问任何主线程对象。相反,您应该保存对象的objectID并使用它进行重新提取。您无法在块之外传递managedObject,但是您传递了其中的数据。

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

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

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