1
votes

Extremely need an advice, currently run out of ideas. I stack with core data concurrency related issue, to debug I use -"com.apple.CoreData.ConcurrencyDebug" and what I have:

Stack:

Thread 3 Queue: coredata(serial)

0 +[NSManagedObjectContext Multithreading_Violation_AllThatIsLeftToUsIsHonor]: CoreData`-[NSManagedObjectContext executeFetchRequest:error:]:

1 -[NSManagedObjectContext executeFetchRequest:error:]:

2 NSManagedObjectContext.fetch (__ObjC.NSFetchRequest) throws -> Swift.Array:

3 AppDelegate.(fetchRequest NSFetchRequest) -> [A]).(closure #1)

I get into AppDelegate::fetchRequest from here:

let messageRequest: NSFetchRequest<ZMessage> = ZMessage.fetchRequest();
messageRequest.sortDescriptors = [NSSortDescriptor(key: "id", ascending: false)];
let messageArray: Array<ZMessage> = self.fetchRequest(messageRequest);

I perform all coredata stuff on the serial queue(self.queueContainer).

public func fetchRequest<T>(_ request: NSFetchRequest<T>) -> Array<T>
{
    var retval: Array<T> = Array<T>();      
    self.queueContainer.sync {
        do {
            retval = try self.persistentContainer.viewContext.fetch(request);
        } catch {
            let nserror = error as NSError;
            fatalError("[CoreData] Unresolved fetch error \(nserror), \(nserror.userInfo)");
        }
    }
    return retval;
}

This is what I found useful.

Below are some of the rules that must be followed if you do not want your app that uses CoreData to crash (or) corrupt the database:

A NSManagedObjectContext should be used only on the queue that is associated with it.

If initialized with .PrivateQueueConcurrencyType, a private, internal queue is created that is associated with the object. This queue can be accessed by instance methods .performBlockAndWait (for sync ops) and .performBlock (for async ops)

If initialized with .MainQueueConcurrencyType, the object can be used only on the main queue. The same instance methods ( performBlock and performBlockAndQueue) can be used here as well. An NSManagedObject should not be used outside the thread in which it is initialized

Now I'm digging around but honestly speaking can't be sure that my managed object context(MOC) is associated with the right queue.

From manual:

...A consequence of this is that a context assumes the default owner is the thread or queue that allocated it—this is determined by the thread that calls its init method.

In the AppDelegate I do not operate with MOC directly, instead of this I instantiating NSPersistentContainer which owns this MOC. Just in case I'm doing this on the same serial queue as well.

public lazy var persistentContainer: NSPersistentContainer =
{
    self.queueContainer.sync {
        let container = NSPersistentContainer(name: "Joker")
        container.loadPersistentStores(completionHandler: { (storeDescription, error) in
            if let error = error as NSError? {
                fatalError("Unresolved error \(error), \(error.userInfo)")
            }
        })

        return container
    }
}()

Thanks in advance.

1

1 Answers

1
votes

I am not a Swift coder , but What is queueContainer?

You should not do the threading yourself, you should use the NSManagedObjectContext block methods as you wrote in your quotation:

The same instance methods ( performBlock and performBlockAndQueue) can be used here as well.

managedObjectContext.performBlock {

Whatever managedObjectContext you are using, you should use that context block method and do your stuff inside the block methods.

Look at the documentation here for examples on how to do this correctly.

Also to avoid crashes and threading errors:

NSManagedObject instances are not intended to be passed between queues. Doing so can result in corruption of the data and termination of the application. When it is necessary to hand off a managed object reference from one queue to another, it must be done through NSManagedObjectID instances.

You retrieve the managed object ID of a managed object by calling the objectID method on the NSManagedObject instance.