Reputation: 519
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
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