Griffin
Griffin

Reputation: 31

Swift 3 / iOS 10, Proper Core Data Usage

Before upgrading my apps to Swift 3 and iOS 10, I had no problems with using CoreData as a data store for simple objects. Lightweight Migrations were simple, saving and fetching was simple, ect. But ever since the recent upgrade, I have had nothing but trouble with CoreData.

My question is in two parts. First, does anyone know of any good resources to help me learn how CoreData works behind the scenes so I can get better at debugging it? Apple's docs are extremely limiting, and all the articles I read act like the new CoreData is so simple. I have decent experience with relational databases, so CoreData is adding an uncomfortable layer of abstraction for me.

Second, what is wrong with the following code? Lightweight migrations aren't working as they did before iOS 10, using this code. Objects are saving to CoreData (I can interact with them in app after saving), but then disappear after the app is restarted.

lazy var persistentContainer: NSPersistentContainer = {

    let description = NSPersistentStoreDescription()

    description.shouldInferMappingModelAutomatically = true
    description.shouldMigrateStoreAutomatically = true

    let container = NSPersistentContainer(name: "MY_APP")
    container.persistentStoreDescriptions = [description]
    let description = NSPersistentStoreDescription()

    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)")
        }
    })
    return container
}()

// MARK: - Core Data Saving support

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

I'm using a separate file to abstract the storing of my objects:

class Repository
{


func getContext () -> NSManagedObjectContext
{
    let appDelegate = UIApplication.shared.delegate as! AppDelegate
    return appDelegate.persistentContainer.viewContext
}


func delete<T>(_ a: some T)
{

    getContext().delete(a as! NSManagedObject)

}


// -----------   Password Repo ----------


func savePassword(name: String, hint: String, un: String) {
    let context = getContext()

    //retrieve the entity that we just created
    let entity =  NSEntityDescription.entity(forEntityName: "Password", in: context)

    let transc = NSManagedObject(entity: entity!, insertInto: context)

    //set the entity values
    transc.setValue(name, forKey: "name")
    transc.setValue(hint, forKey: "thing")
    transc.setValue(un, forKey: "newThing")


    //save the object
    do {
        try context.save()
    } catch let error as NSError  {
        print("Could not save \(error), \(error.userInfo)")
    } catch {

    }
}


func updatePassword(pas: Password) -> Password
{
    let context = getContext()
    //        sec.is_new = false

    // TODO,  add updates

    // Try updating the model in the DB
    do {
        try context.save()
    } catch {
        print(error)

    }

    return pas
}


func fetchPasswords() -> [Password]
{

    let context = getContext()
    //create a fetch request, telling it about the entity
    let fetchRequest: NSFetchRequest<Password> = Password.fetchRequest() as! NSFetchRequest<Password>
    let sortDescriptor = NSSortDescriptor(key: "name", ascending: true)
    fetchRequest.sortDescriptors = [sortDescriptor]

    do {
        //go get the results
        let searchResults = try getContext().fetch(fetchRequest)
        return searchResults

    } catch {
        print("Error with request: \(error)")
    }

    return []
}




// -----------   End Password Repo ----------





// -----------   Hints Repo ----------

func saveHint (name: String, hint: String) {
    let context = getContext()

    //retrieve the entity that we just created
    let entity =  NSEntityDescription.entity(forEntityName: "Hint", in: context)

    let transc = NSManagedObject(entity: entity!, insertInto: context)

    //set the entity values
    transc.setValue(value1, forKey: "some_string")
    transc.setValue(value2, forKey: "some_thing")

    //save the object
    do {
        try context.save()
    } catch let error as NSError  {
        print("Could not save \(error), \(error.userInfo)")
    } catch {

    }
}




func fetchHints() -> [Hint]
{

    let context = getContext()
    //create a fetch request, telling it about the entity
    let fetchRequest: NSFetchRequest<Hint> = Hint.fetchRequest() as! NSFetchRequest<Hint>
    let sortDescriptor = NSSortDescriptor(key: "my_key", ascending: true)
    fetchRequest.sortDescriptors = [sortDescriptor]

    do {
        //go get the results
        let searchResults = try getContext().fetch(fetchRequest)
        return searchResults

    } catch {
        print("Error with request: \(error)")
    }

    return []
}

}

Then I call this Repository class like so:

Repository().savePassword(name: nameText.text!, hint: hintSoFarLabel.text!, un: "Hey")

The Repository class is working, until I restart the app...

I'm also trying to migrate to a new version of my Core Data Model which simply adds a non-optional String attribute (with default value), which would be a simple lightweight migration in iOS 9/Swift 2. What am I missing about swift 3 lightweight migrations?

I have no doubt that the problem is me not understanding CoreData in iOS 10 well enough. I've been a software engineer for a while, but I've only been working with iOS and Swift for a few months, so please be gentle. And thank's in advance!

Upvotes: 2

Views: 434

Answers (1)

Griffin
Griffin

Reputation: 31

Well, I feel like an idiot. I added a non-optional string and didn't give it a default value. I always gave default values to non-string types, but the way Xcode is set up made it appear that it would just give strings a default value of "" if no other was given.

Adding a default value to the new attribute allowed the migration to work in Swift3/iOS10. Noob mistake, but maybe it will help someone here in the future.

Upvotes: 1

Related Questions