iOS data saved in Core Data doesn't survive launch

Viewed 383

EDIT *********

7-09-2020 1:39 PM PST

I've got what I believe will suffice as a minimal working reproducible version of the app available at:

https://github.com/Rattletrap99/penny-game-test

EDIT *********

I'm building a game in which users create coins as rewards for various achievements. The coins are saved as managed objects in Core Data, with their various attributes. They are retrieved for various reasons, have their attributes modified, etc., during game play.

Everything saves and retrieves perfectly until I quit and relaunch, at which point no data is present in the persistent store. This is the case whether using a simulator or a device.

My usual means of saving to Core Data is:

func saveIt(){
    guard let appDelegate =
        UIApplication.shared.delegate as? AppDelegate else {
            return
    }
    appDelegate.saveContext()
}

Which calls:

func saveContext () {
    let context = persistentContainer.viewContext
    if context.hasChanges {
        do {
            try context.save()
            savedFlag += 1
            
            let coinFetchRequest =
                NSFetchRequest<NSManagedObject>(entityName: "Coin")

            let savedCoins = try context.fetch(coinFetchRequest) as! [Coin]
            
            print("In appDelegate, saveContext, after context.save, there are \(savedCoins.count) coins.")
            print("Successfully saved in appDelegate \(String(describing: savedFlag)) times")

            
            
        } catch {
            // Replace this implementation with code to handle the error appropriately.
            // fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
            let nserror = error as NSError
            fatalError("Unresolved error \(nserror), \(nserror.userInfo)")
        }
    }
}

Every print() statement I put in the code confirms the save, but when I retrieve (upon relaunch), via code similar to:

    let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
    let issuerFetchRequest =
        NSFetchRequest<NSManagedObject>(entityName: "Issuer")
    let coinFetchRequest =
        NSFetchRequest<NSManagedObject>(entityName: "Coin")

    
    do {
        let issuers = try context.fetch(issuerFetchRequest) as! [Issuer]
        print(" ###   Upon startup, there are \(issuers.count) Issuers in CD")
        
        let coins = try context.fetch(coinFetchRequest) as! [Coin]
        print(" ####   Upon startup, there are \(coins.count) Coins in CD")

    } catch let error as NSError {
        print("Could not fetch. \(error), \(error.userInfo)")
    }

I get:

 ###   Upon startup, there are 0 Issuers in CD
 ####   Upon startup, there are 0 Coins in CD

Also, I try to save in applicationWillTerminate to be certain the data is saved before quitting:

func applicationWillTerminate(_ application: UIApplication) {
    // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground.
    // Saves changes in the application's managed object context before the application terminates.
    
    
    self.saveContext()
    
    let issuerFetchRequest =
        NSFetchRequest<NSManagedObject>(entityName: "Issuer")
    let coinFetchRequest =
        NSFetchRequest<NSManagedObject>(entityName: "Coin")

    do {
        let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext

        let issuers = try context.fetch(issuerFetchRequest) as! [Issuer]
        let coins = try context.fetch(coinFetchRequest) as! [Coin]

        print("\\\\ Upon quit, there are \(issuers.count) Issuers in CD")
        print("\\\\ Upon quit, there are \(coins.count) Coins in CD")

    } catch let error as NSError {
        print("Could not fetch. \(error), \(error.userInfo)")
    }
}

However, the print() statements are not printed, leading me to believe applicationWillTerminate is never fired.

I should mention that Issuer has a to many relationship with Coin, and I ensure that there are Issuers present before creating and saving a Coin.

Any help greatly appreciated!

2 Answers

After beating myself half to death for longer than I care to admit, I found the fault in these three lines in AppDelegate.swift:

    description.shouldInferMappingModelAutomatically = true
    description.shouldMigrateStoreAutomatically = true
    container.persistentStoreDescriptions = [description]

Once these were removed, everything went back to normal. I'd like to say I understand why, but honestly, removing these lines were an act of desperation. And the irony is that I had added these lines in an attempt to rectify an earlier problem with fetching from Core Data.

Many thanks to all who contributed!

Remember to load up the persistentContainer in your AppDelegate. You can create a project in Xcode with CoreData built-in.

// MARK: - Core Data stack

lazy var persistentContainer: NSPersistentContainer = {
    /*
     The persistent container for the application. This implementation
     creates and returns a container, having loaded 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.
    */
    let container = NSPersistentContainer(name: "CoreDataModelName")
    container.loadPersistentStores(completionHandler: { (storeDescription, error) in
        if let error = error as NSError? {
            // Replace this implementation with code to handle the error appropriately.
            // fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
             
            /*
             Typical reasons for an error here include:
             * The parent directory does not exist, cannot be created, or disallows writing.
             * The persistent store is not accessible, due to permissions or data protection when the device is locked.
             * The device is out of space.
             * The store could not be migrated to the current model version.
             Check the error message to determine what the actual problem was.
             */
            fatalError("Unresolved error \(error), \(error.userInfo)")
        }
    })
    return container
}()

// MARK: - Core Data Saving support

func saveContext () {
    let context = persistentContainer.viewContext
    if context.hasChanges {
        do {
            try context.save()
        } catch {
            // Replace this implementation with code to handle the error appropriately.
            // fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
            let nserror = error as NSError
            fatalError("Unresolved error \(nserror), \(nserror.userInfo)")
        }
    }
}
Related