简体   繁体   中英

Core Data Concurrency issue in Swift 4

I run into the problem of core data concurrency when retrieving a NSManagedObj from core data when the thread is not main thread.

I played around with core data of Swift 4 and everything worked well initially when the codes were simple. However, when I added more codes for core data and increased the the complexity, I ran into an error by occasion and discovered the concurrency issue of core data.

The error that I ran into was

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

I searched some ways trying to solve the issue by adding the below line to handle the retrieving core data action when it comes to the background thread instead of the main thread.

appDelegate.persistentContainer.performBackgroundTask{....}

However, it still has problems.

First, here is the retrieve function

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
}

And here is the retrieve function in 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
}

Now, something weird is found: The MasterSlave entity should have only one object which I saved once before. So when it runs let ms = appDelegate.retrieve("MasterSlave", predicate: nil, sort: nil, limit: nil)! , it is expected to retrieve the following data

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

But it gives the following instead

[<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;
})]

When I stop the app and deploy again, the count of the [NSManagedObject] of masterSlave become 18 instead of 17.

I don't understand why the count of the retrieved [NSManagedObject] increments each time the deployment is built. Also, when it is in main thread, things work properly.

Does anyone have any ideas?

Core-data is not thread safe. Neither for reading or for writing. Every context has one (and only one) thread that it is safe to access from. For the viewContext it is the main thread. For contexts created by performBackgroundTask it should only be accessed inside that block. This is true both for the context and all managedObjects associated with the context.

Your retrieve method, where you fetch from core-data, you should accept a context as a parameter. When it is used from the main they it should be passed the viewContext; when it is from a performBackgroundTask it should use the context that was passed to the block.

You cannot pass objects in to or out of a performBackgroundTask block. So you should not access any main-thread object from inside a performBackgroundTask. Instead you should save the object's objectID and refetch using it. You cannot pass managedObjects outside of the block, but you pass the data that inside of them.

Your use of blocks in retrieve() is nonsensical. The method returns before either block is executed and masterSlave will alway be nil. Generally, you should fetch on the main thread from a method that you know is already on the main thread. Using DispatchQueue.main.async to make a fetch and then try to send that data back to a background thread is never a good idea - you generally want the information on the main thread where you can display the information.

If you want to guarantee that there will be only one instance of an entity you should do a fetch for that object before you create it. It looks like you are creating a new object every time the app launches. You have not shared that code so I cannot comment further.

If you expect to have less than around a hundred entities reading a writing only on the viewContext is not an unreasonable setup. If that is the case I would recommend doing so as it would fix most of your issues.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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