CM.
CM.

Reputation: 519

Core Data update old items, create new records

I am building an iOS app with iOS 9/Xcode 7/ Swift 2

The primary datastore is Cloudkit, and this works fine (but slowly) and allows data to be shared among users.

To speed up the process for the majority of queries, Core Data is being used locally.

When updating the Core Data, two states occur - 1. A new record is added from CloudKit to Core data (this appears to work fine) 2. An existing Core Data record is updated from CloudKit (this is the step that is problematic!)

Should the existing Core Data record be deleted and then a new one created, or some other solution?

Here is the code so far -

let entityDescription = NSEntityDescription.entityForName("FoodItem", inManagedObjectContext: self.managedObjectContext)

    let localFoodItem = NSManagedObject(entity: entityDescription!, insertIntoManagedObjectContext: self.managedObjectContext)

    let syncpredicate = NSPredicate(value: true)

    let syncquery = CKQuery(recordType: "Food", predicate: syncpredicate)

publicDatabase.performQuery(syncquery, inZoneWithID: nil) { results, error in
        if error == nil { // There is no error
            var numberAdded = 0
            for entry in results! {

                let cloudUPC = entry["UPC"] as? String
                //print("UPC from CloudKit \(cloudUPC)")
                let cloudFoodName = entry["foodName"] as? String
                //print("Name from CloudKit \(cloudFoodName)")
                let cloudIngredients = entry["ingredients"] as? String
                //print("Ingredients from CloudKit \(cloudFoodName)")
                numberAdded++


                let corepredicate = NSPredicate(format: "upc = %@", cloudUPC!)


                // Initialize Fetch Request
                let fetchRequest = NSFetchRequest()

                fetchRequest.predicate = corepredicate
                fetchRequest.entity = entityDescription

                var error: NSError?

                do {
                    let result = try self.managedObjectContext.executeFetchRequest(fetchRequest)


                    if result.count > 0 {
                        let match = result[0] as! NSManagedObject
                        print("FoodItem Found in CoreData")
                        let upcNumber = match.valueForKey("upc") as! String


                        let iList = match.valueForKey("ingredients") as! String



                        let coreName = match.valueForKey("productName") as! String

print("Item UPDATED in CoreData")
                            localFoodItem.setValue(cloudUPC, forKey: "upc")
                            localFoodItem.setValue(cloudFoodName, forKey: "productName")
                            localFoodItem.setValue(cloudIngredients, forKey: "ingredients")


                    } else {
                        print("IMPORTED New Item from CloudKit")
                        localFoodItem.setValue(cloudUPC, forKey: "upc")
                        localFoodItem.setValue(cloudFoodName, forKey: "productName")
                        localFoodItem.setValue(cloudIngredients, forKey: "ingredients")
                        //print("Record Update: \(numberAdded)")
                    }


                } catch let error as NSError {
                    // failure
                    print("Fetch failed: \(error.localizedDescription)")
                }


            do {
                try self.managedObjectContext.save()
            } catch {
                print(error)
            }
            }
            print("Database Additions: \(numberAdded)")
        }else {
            print(error)
        }

    }

Upvotes: 0

Views: 610

Answers (1)

pbasdf
pbasdf

Reputation: 21536

It is this line:

let localFoodItem = NSManagedObject(entity: entityDescription!, insertIntoManagedObjectContext: self.managedObjectContext)

that creates the new CoreData record. Each iteration of the subsequent for loop just changes the attribute values for this one record, so even if there are several items to sync from CloudKit, only one new CoreData record will be created. The above line should be within the for loop. Since you only want to create a new CoreData object if there is no pre-existing object with the same upc, the above line should be in the else clause. If there is a pre-existing object, I assume you just want to update the other attributes for that object with the new values from CloudKit:

let entityDescription = NSEntityDescription.entityForName("FoodItem", inManagedObjectContext: self.managedObjectContext)
let syncpredicate = NSPredicate(value: true)
let syncquery = CKQuery(recordType: "Food", predicate: syncpredicate)
publicDatabase.performQuery(syncquery, inZoneWithID: nil) { results, error in
    if error == nil { // There is no error
        var numberAdded = 0
        var numberUpdated = 0
        for entry in results! {
            let cloudUPC = entry["UPC"] as? String
            //print("UPC from CloudKit \(cloudUPC)")
            let cloudFoodName = entry["foodName"] as? String
            //print("Name from CloudKit \(cloudFoodName)")
            let cloudIngredients = entry["ingredients"] as? String
            //print("Ingredients from CloudKit \(cloudFoodName)")

            let corepredicate = NSPredicate(format: "upc = %@", cloudUPC!)
            // Initialize Fetch Request
            let fetchRequest = NSFetchRequest()
            fetchRequest.predicate = corepredicate
            fetchRequest.entity = entityDescription

            var error: NSError?
            do {
                let result = try self.managedObjectContext.executeFetchRequest(fetchRequest)
                if result.count > 0 {
                    let match = result[0] as! NSManagedObject
                    print("FoodItem Found in CoreData")
                    // The next three lines seem superfluous?
                    let upcNumber = match.valueForKey("upc") as! String
                    let iList = match.valueForKey("ingredients") as! String
                    let coreName = match.valueForKey("productName") as! String
                    print("Item UPDATED in CoreData")
                    match.setValue(cloudFoodName, forKey: "productName")
                    match.setValue(cloudIngredients, forKey: "ingredients")
                    numberUpdated++
                } else {
                    print("IMPORTED New Item from CloudKit")
                    let localFoodItem = NSManagedObject(entity: entityDescription!, insertIntoManagedObjectContext: self.managedObjectContext)
                    localFoodItem.setValue(cloudUPC, forKey: "upc")
                    localFoodItem.setValue(cloudFoodName, forKey: "productName")
                    localFoodItem.setValue(cloudIngredients, forKey: "ingredients")
                    numberAdded++
                }
            } catch let error as NSError {
                // failure
                print("Fetch failed: \(error.localizedDescription)")
            }
        }
        do {
            try self.managedObjectContext.save()
            print("Database Additions: \(numberAdded)")
            print("Database Updates: \(numberUpdated)")
        } catch {
            print(error)
        }
    }else {
        print(error)
    }
}

Note that I would move the save operation so it is after, not within, the for loop. Also, you might find you have CoreData threading/concurrency issues if the CloudKit completion handler is not executed on the main thread (I'm not familiar with CK so can't advise).

Upvotes: 1

Related Questions