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:
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
}
}
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
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With