2
votes

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
could you commit a minimal reproducible version of code to git? - nghiahoang
Also, is your core data store backed by sqlite? You could try to inspect the sqlite file with a sqlite browser to check if the data is properly stored there. If it is, then you can narrow down to the retrieval parts of your code. - auspicious99
Are you sure that you are using the correct fetch request? The docs say The model associated with the context’s persistent store coordinator must contain an entity named entityName. Or, every entity in your 'xcdatamodel' has a corresponding class with the same name. You could try to use 'let yourEntityFetchRequest: NSFetchRequest<YourClass> = YourClass.fetchRequest()', see here. - Reinhard Männer
@ReinhardMänner -- Working minimal at: github.com/Rattletrap99/penny-game-test. However, I seem not to be able to add the xcdatamodel file, despite several tries. Any guidance appreciated... - rattletrap99
@ReinhardMänner -- I tried changing the fetchRequest according to your suggestion, but the results are the same. Zero data in the persistent store... - rattletrap99

2 Answers

0
votes

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)")
        }
    }
}
0
votes

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!