Search code examples
iosswiftcore-dataconcurrencynsmanagedobjectcontext

How to use Core Data managedObjectContext properly and a proper way to save context?


In my app, I have a special CoreDataManager class that is responsible for all Core Data operations in my app.

The first lines in CoreDataManager Class look like this:

   static var sharedInstance: CoreDataManager = { // or private static var
        let instance = CoreDataManager()
        return instance
    }()

    // Init
    override init() {
        self.persistentContainer = NSPersistentContainer.init(name: "AppName")
        self.persistentContainer.loadPersistentStores { (storeDescription, error) in
            if let error = error as NSError? {
                fatalError("CoreDataManager: init, loadPersistentStores error: \(error), \(error.userInfo)")
            } else {
                print("CoreDataManager: init() func finished with Success!")


            }
        }

        self.persistentContainer.viewContext.undoManager = nil 
        self.persistentContainer.viewContext.shouldDeleteInaccessibleFaults = true
        self.persistentContainer.viewContext.automaticallyMergesChangesFromParent = true

    }

    let persistentContainer: NSPersistentContainer
    var viewContext: NSManagedObjectContext? {
        return self.persistentContainer.viewContext
    }


    func saveViewContext() {

        let context = self.viewContext

        if self.viewContext != nil {

            if context!.hasChanges {
                do {
                    try context!.save()

                } catch {

                    let nserror = error as NSError
                    fatalError("Unresolved error \(nserror), \(nserror.userInfo)")
                }

            }


        }

When I need to save, edit or delete core data entities in my app I call a singleton of CoreDataManager class, and methods of (saving, editing, deletion of entities) declared in this class.

In my app I also have a LocalNotificationManager, where I do some operations with CoreData entities when I receive changes from CloudKit or when I delete some entities. I'm calling CoreDataManager.sharedInstance.saveViewContext() when I want to save changes, and " think that I'm doing it wrong because sometimes I get errors.

I don't clearly understand how to properly call context and save it, and have the following questions:

In my save, edit, delete calls, should I use/save mainContext or privateContext?

When notifications received, should I use/save mainContext or privateContext?

When I should save viewContext from the main thread, and when to use private managed object context? Should I use .MainQueueConcurrencyType or I can avoid it?

Should I declare a privateObjectContext variable in my CoreDataManager class and which is the proper way to do this?

I've seen that private context can be declared this way, but can I implement it in my CoreDataManager class and when should I call it instead of saveViewContext().

 let moc = NSManagedObjectContext(concurrencyType:.MainQueueConcurrencyType)
 let privateMOC = NSManagedObjectContext(concurrencyType: .PrivateQueueConcurrencyType)
 privateMOC.parentContext = moc
 privateMOC.performBlock({
     do {
         try privateMOC.save()
     } catch {
         fatalError("Failure to save context: \(error)")
     }
     })

Can anyone explain the proper usage of saving context in different situations clearly, please?

Swift 4, Xcode 9.


Solution

  • You have two options:

    The simple way

    Read and write ONLY to the viewContext and ONLY from the main thread. This will work but if you have a lot of data that you are reading or writing at one time it will block your UI. If you have an app with a small amount of stuff stored in core-data this will work fine and is by far the simplest option.

    The complex way

    Do all of your reading with the viewContext but NEVER write to it. Only access the viewContext from the main thread. Do all of your writing with persistentContainer.performBackgroundTask and use the context that was pass into the block. Do not use any other managedObjects inside this block. If you need to then pass an objectId and do a fetch inside the block. Don't pass any objects outside of these blocks. If you find that you need to, then use objectId. You also need to create an operationQueue and wrap all you calls to performBackgroundTask inside of it so you don't get any write conflicts. A fuller description can be found here: NSPersistentContainer concurrency for saving to core data