0

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.

Nitish
  • 13,845
  • 28
  • 135
  • 263
Adelmaer
  • 2,209
  • 3
  • 22
  • 45
  • 1
    privateObjectContext is for using the context on a background thread, unless you're seeing performance issues or UI lock ups your fine using your standard viewContext on the main thread. – Joakim Danielson May 30 '18 at 11:41

1 Answers1

4

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

Jon Rose
  • 8,373
  • 1
  • 30
  • 36
  • Thanks, for the simple way, can you please explain: In my ViewController (VC) I make a writing (edit/delete) (using writeFunction()) and reading (fetching with returning array of core data objects) calls (using readFunction()). In my VC I run writeFunction() and readFunction() on a main thread: `DispatchQueue.main.async { CoreDataManager.sharedInstance.readFunction() // same for writeFunction() }` Should I call `saveViewContext()` inside a writeFunction() on the main thread with `DispatchQueue.main.async`? Or it will be enough to use DispatchQueue.main.async only in a VC? – Adelmaer May 31 '18 at 13:15
  • if you are doing the complex way you should never EVER save the viewContext because you should never write to it in the first place. For the performBackgroundTask context you should save them inside the block that was pass - do not dispatch it to any other thread. – Jon Rose May 31 '18 at 13:40
  • I think I'll go "a simple way" Jon. So, In the question above I asked about "a simple way". For "a simple way" that you described earlier, will it be a right technique to make a `DispatchQueue.main.async { CoreDataManager.sharedInstance.writeFunction() }` call from VC, and inside writeFunction(), should I call CoreDataManager.sharedInstance.saveViewContext() or it should be DispatchQueue.main.async { CoreDataManager.sharedInstance.saveViewContext() }? – Adelmaer May 31 '18 at 14:00
  • 1
    If you are calling from a viewController you are generally already on the main thread, calling `DispatchQueue.main.async` would be unnecessary, but, besides delaying execution, it doesn't do a much harm. If `CoreDataManager.sharedInstance.writeFunction` is always called from the main thread then calling `DispatchQueue.main.async` is also unnecessary, but again, wouldn't do much harm besides making the execution slower. You can test if you are on the main thread by using `Thread.isMainThread` if you are in a state that you don't know. – Jon Rose May 31 '18 at 14:07
  • Great, that helped a lot Jon – Adelmaer May 31 '18 at 14:11
  • The simple way is incorrect here. Apple states in the documentation that the viewContext is read-only: "The managed object context associated with the main queue. (read-only)". I haven't tried saving to it myself, but even if it works it clearly says you shouldn't do it. – Brian M Dec 14 '18 at 15:38
  • 2
    It is read only in the sense that you cannot assign a different context to be the viewContext, not that you cannot call viewContext.save() on it. – Jon Rose Dec 16 '18 at 10:57