Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why am I getting a crash (Enqueued from com.apple.main-thread (Thread 1)) when calling `privateManagedObjectContext.perform`?

In my closure of my network request, I am inserting objects into core data using a private concurrency queue and getting a crash when I call "perform" on the private context.

Crash message in console:

libc++abi.dylib: terminating with uncaught exception of type NSException

Stack Trace: enter image description here

The code that's causing the crash:

API.sync(onlyMe, syncToken: syncToken) { success, syncResponse in
    CoreDataUtils.privateContext.perform { // crashes on this line
        ....
    }
}

My Core Data stack (unfortunately located in the AppDelegate at the moment and not in a CoreDataStack class):

lazy var persistentStoreCoordinator: NSPersistentStoreCoordinator = {
    // The persistent store coordinator for the application. This implementation creates and returns a coordinator, having added the store for the application to it. This property is optional since there are legitimate error conditions that could cause the creation of the store to fail.
    // Create the coordinator and store
    var coordinator = NSPersistentStoreCoordinator(managedObjectModel: self.managedObjectModel)
    let url = self.applicationDocumentsDirectory.appendingPathComponent("SingleViewCoreData.sqlite")
    let options = [NSMigratePersistentStoresAutomaticallyOption: true,
                    NSInferMappingModelAutomaticallyOption: true]
    do {
        try coordinator.addPersistentStore(ofType: NSSQLiteStoreType, configurationName: nil, at: url, options: options)
    } catch {
        print(error)
    }

    return coordinator
}()

lazy var privateManagedObjectContext: NSManagedObjectContext = {
    // Initialize Managed Object Context
    var managedObjectContext = NSManagedObjectContext(concurrencyType: .privateQueueConcurrencyType)

    // Configure Managed Object Context
    managedObjectContext.parent = self.managedObjectContext

    return managedObjectContext
}()

lazy var managedObjectContext: NSManagedObjectContext = {
    // Returns the managed object context for the application (which is already bound to the persistent store coordinator for the application.) This property is optional since there are legitimate error conditions that could cause the creation of the context to fail.
    var managedObjectContext = NSManagedObjectContext(concurrencyType: .mainQueueConcurrencyType)
    managedObjectContext.persistentStoreCoordinator = self.persistentStoreCoordinator
            managedObjectContext.mergePolicy = NSRollbackMergePolicy //This policy discards in-memory state changes for objects in conflict. The persistent store’s version of the objects’ state is used

    return managedObjectContext
}()

Where CoreDataUtils.privateContext is:

class CoreDataUtils: NSObject {

    static let appDel = UIApplication.shared.delegate as! AppDelegate
    static let context: NSManagedObjectContext { 
        return appDel.managedObjectContext 
    }
    static let privateContext: NSManagedObjectContext {   
        appDel.privateManagedObjectContext 
    }
}
like image 342
Michael McKenna Avatar asked Nov 07 '22 06:11

Michael McKenna


1 Answers

I followed @CodeBender's link and found that there's several locations where I am getting a a multithreading violation. The link provides a way to debug concurrency issues by passing arguments in your project's scheme. This method provides better detail on what went wrong and where.

For example I have a function that performs a fetch, and I did not wrap the code in a perform block. This is how I implemented a fix:

static func searchObject(in context: NSManagedObjectContext = context, _ entity: String, key: String, value: Any) -> [NSManagedObject]? {

    var objects: [NSManagedObject]?

    context.performAndWait {
        //searching core data for a specific attribute within an identity via a predicate
        let request = NSFetchRequest<NSFetchRequestResult>(entityName: "\(entity)")
        request.returnsObjectsAsFaults = false
        request.predicate = NSPredicate(format: "\(key) == %@", "\(value)")

        do {
            let results = try context.fetch(request)
            objects = results as? [NSManagedObject]
        } catch {
            let nserror = error as NSError
            Bugsnag.notifyError(nserror)
            NSLog("Unresolved error \(nserror), \(nserror.userInfo)")
            abort()
        }
    }


    return objects
}

Also, in another spot where I am inserting Notification objects from the server, I was calling the perform block but I was not instantiating the managed objects with the proper context.

CoreDataUtils.privateContext.perform {
    for notification in notifications {

        // BEFORE (WRONG) - would default to the main context (CoreDataUtils.context - see question)
        //notification.insert()

        // NOW (CORRECT) - inserts into private queue we are performing on
        notification.insert(into: CoreDataUtils.privateContext)
    }

    CoreDataUtils.save()
}

Where in the Notification model I have:

func insert(into context: NSManagedObjectContext = CoreDataUtils.context) -> NotificationObject {
    assert(CoreDataUtils.searchObject(Models.notification, key: "id", value: self.id) == nil)
    let newNotification = NotificationObject(notification: self, context: managedObjectContext)
    return newNotification
}
like image 76
Michael McKenna Avatar answered Nov 14 '22 23:11

Michael McKenna