Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Data not persistent in Core Data when app re-launches

I am using Core Data for the first time in a project made in XCode 8, swift 3. I have used background context (calling container.performBackgroundTask block..) to save the data and main context to fetch the data. When my app re-launches, the data I saved in the private background context is deleted.

Please tell me where I went wrong !!!

Here I call CoreDataManager class save context method in applicationDidEnterBackground and applicationWillTerminate methods of AppDelegate class:

class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
lazy var coreDataMgrInstance = CoreDataManager.sharedInstance

func applicationDidEnterBackground(_ application: UIApplication) {

    coreDataMgrInstance.saveContext()
}
func applicationWillTerminate(_ application: UIApplication) {
    coreDataMgrInstance.saveContext()
}}

Here is my Singleton class CoreDataManager to initiate NSpersistentContainer

class CoreDataManager: NSObject {    
class var sharedInstance: CoreDataManager {
    struct Singleton {
        static let instance = CoreDataManager()
    }

    return Singleton.instance
}
   private override init() {

    super.init()
}

 lazy var persistentContainer: NSPersistentContainer = {

   let container = NSPersistentContainer(name: "E_CareV2")

    let description = NSPersistentStoreDescription() // enable auto lightweight migratn
    description.shouldInferMappingModelAutomatically = true
    description.shouldMigrateStoreAutomatically = true

    container.persistentStoreDescriptions = [description]

    container.loadPersistentStores(completionHandler: { (storeDescription, error) in
        if let error = error as NSError? {
            fatalError("Unresolved error \(error), \(error.userInfo)")
        }
    })
    container.viewContext.automaticallyMergesChangesFromParent = true
    return container
}()
func saveContext(){

    print("saveContext")
    let context = persistentContainer.viewContext
    if context.hasChanges {
        do {
            try context.save()
        } catch {
            let nserror = error as NSError
            fatalError("Failure to save main context \(nserror), \(nserror.userInfo)")
        }
}}

Now this is the class where I save and fetch the data from Core Data

class SenderViewController: UIViewController {

var persistentContainer: NSPersistentContainer!

override func viewDidLoad() {
    super.viewDidLoad()

 persistentContainer = CoreDataManager.sharedInstance.persistentContainer
 let results = self.fetchPersistentData(entityName: "School", withPredicate: nil)
    print("results \n \(results)")       
  }

 @IBAction func enterPressed(_ sender: Any) {

  self.persistentContainer.performBackgroundTask({ (backgroundContext) in

        backgroundContext.mergePolicy = NSMergePolicy.mergeByPropertyStoreTrump

        let schoolentity = NSEntityDescription.insertNewObject(forEntityName: "School", into: backgroundContext) as! School

                schoolentity.schoolName = "ABC"
                schoolentity.schoolWebSite = "http://anywebsite.com/"

            do {
                try backgroundContext.save()
            } catch {
                fatalError("Failure to save background context: \(error)")
                }
    })
}

func fetchPersistentData(entityName: String?, withPredicate: NSPredicate?) -> [NSManagedObject]{

        let context = self.persistentContainer.viewContext
        let request: NSFetchRequest<School> = School.fetchRequest()
        let newentity = NSEntityDescription.entity(forEntityName: entityName!, in: context)

        request.entity = newentity
        request.returnsObjectsAsFaults = false

        do {
            let searchResults = try context.fetch(request) as [NSManagedObject]
            return searchResults               
        } catch {
            print("Error with request: \(error)")
        }
 return []
}
like image 277
Nupur Sharma Avatar asked Dec 24 '22 16:12

Nupur Sharma


2 Answers

Actually the lightweight migrations are enabled by default as you can see on the screenshotenter image description here

So you can safely delete these lines:

let description = NSPersistentStoreDescription() // enable auto lightweight migratn
description.shouldInferMappingModelAutomatically = true
description.shouldMigrateStoreAutomatically = true

container.persistentStoreDescriptions = [description]

After that everything should work.

like image 108
Sergey Pekar Avatar answered Jan 25 '23 22:01

Sergey Pekar


There are two ways to use NSPersistentContainer - the simple way and the correct way. You are mixing them which is leading to problems. Apple is a little inconsistent on this in its documentation so it is understandable that you are confused.

The Simple Way - The simple way is only use the viewContext both for reading and for writing and only from the main thread. There are never any conflicts because core-data is assessed via a single thread. The problem with this approach is that you can't run anything in a background thread. So if you are importing a lot of data the UI will freeze. If you have a super simple app (a small task list?) this will work OK, but not something that I would ever recommend for a serious app. It is OK for a testing app for a beginner to learn core-data.

The Correct Way - the correct way is to NEVER write to the viewContext EVER. Apple documents this in NSPersistentContainer documentation (but also in its template creates a save method for the viewContext?!). In this setup all writes to core data MUST go through performBackgroundTask and you have to call save on that context before the end of the block. You also need an operation queue to make sure that there are no merge conflicts see NSPersistentContainer concurrency for saving to core data. This setup is a lot harder to do correctly. Objects from performBackgroundTask contexts cannot leave the block and objects from the viewContext cannot be used in the block. The complier doesn't help you with this so it is something that you always need to watch out for.

Your problem is mergePolicy. mergePolicy is evil. If you have conflicts in core-data you have already done something wrong and any merge policy will lose data. In the simple setup there are no conflicts because it is all on one thread. In the correct setup there is no conflicts because of the queue you created when using performBackgroundTask. The problem is that if you use BOTH performBackgroundTask and write the the viewContext you can get conflicts and you will lose data. Personally, I think it is better to have no mergePolicy and crash then to silently lose data.

like image 25
Jon Rose Avatar answered Jan 26 '23 00:01

Jon Rose