diff --git a/Source/Classes/Fetch/FetchAndSaveOperation.swift b/Source/Classes/Fetch/FetchAndSaveOperation.swift index 50995fc2..332b3e63 100644 --- a/Source/Classes/Fetch/FetchAndSaveOperation.swift +++ b/Source/Classes/Fetch/FetchAndSaveOperation.swift @@ -73,10 +73,14 @@ public class FetchAndSaveOperation: Operation { let recordZoneChangesOperation = FetchRecordZoneChangesOperation(from: database, recordZoneIDs: recordZoneIDs, tokens: tokens) + var objectsWithMissingReferences = [MissingReferences]() recordZoneChangesOperation.recordChangedBlock = { // Convert and write CKRecord To NSManagedObject Operation let convertOperation = RecordToCoreDataOperation(parentContext: context, record: $0) convertOperation.errorBlock = { self.errorBlock?($0) } + convertOperation.completionBlock = { + objectsWithMissingReferences.append(convertOperation.missingObjectsPerEntities) + } self.queue.addOperation(convertOperation) } @@ -90,6 +94,44 @@ public class FetchAndSaveOperation: Operation { recordZoneChangesOperation.errorBlock = { zoneID, error in self.handle(recordZoneChangesError: error, in: zoneID, database: database, context: context) } + + recordZoneChangesOperation.completionBlock = { + // iterate over all missing references and fix them, now are all NSManagedObjects created + for missingReferences in objectsWithMissingReferences { + for (object, references) in missingReferences { + guard let serviceAttributes = object.entity.serviceAttributeNames else { continue } + + for (attributeName, recordIDs) in references { + for recordId in recordIDs { + guard let relationship = object.entity.relationshipsByName[attributeName], let targetEntityName = relationship.destinationEntity?.name else { continue } + + let fetchRequest = NSFetchRequest(entityName: targetEntityName) + fetchRequest.predicate = NSPredicate(format: serviceAttributes.recordID + " == %@" , recordId) + fetchRequest.fetchLimit = 1 + fetchRequest.includesPropertyValues = false + + do { + let foundObject = try context.fetch(fetchRequest).first as? NSManagedObject + + if let foundObject = foundObject { + if relationship.isToMany { + let set = object.value(forKey: attributeName) as? NSMutableSet ?? NSMutableSet() + set.add(foundObject) + object.setValue(set, forKey: attributeName) + } else { + object.setValue(foundObject, forKey: attributeName) + } + } else { + print("warning: object not found " + recordId) + } + } catch { + self.errorBlock?(error) + } + } + } + } + } + } queue.addOperation(recordZoneChangesOperation) } diff --git a/Source/Classes/Fetch/SubOperations/RecordToCoreDataOperation.swift b/Source/Classes/Fetch/SubOperations/RecordToCoreDataOperation.swift index bbc99627..d20eecae 100644 --- a/Source/Classes/Fetch/SubOperations/RecordToCoreDataOperation.swift +++ b/Source/Classes/Fetch/SubOperations/RecordToCoreDataOperation.swift @@ -9,11 +9,16 @@ import CoreData import CloudKit +typealias AttributeName = String +typealias RecordID = String +typealias MissingReferences = [NSManagedObject: [AttributeName: [RecordID]]] + /// Convert CKRecord to NSManagedObject and save it to parent context, thread-safe class RecordToCoreDataOperation: AsynchronousOperation { let parentContext: NSManagedObjectContext let record: CKRecord var errorBlock: ErrorBlock? + var missingObjectsPerEntities = MissingReferences() /// - Parameters: /// - parentContext: operation will be safely performed in that context, **operation doesn't save that context** you need to do it manually @@ -78,7 +83,9 @@ class RecordToCoreDataOperation: AsynchronousOperation { let attribute = CloudKitAttribute(value: recordValue, fieldName: key, entityName: entityName, serviceAttributes: serviceAttributeNames, context: context) let coreDataValue = try attribute.makeCoreDataValue() - object.setValue(coreDataValue, forKey: key) + object.setValue(coreDataValue, forKey: key) + + missingObjectsPerEntities[object] = attribute.notFoundRecordIDsForAttribute } // Set system headers diff --git a/Source/Model/CloudKitAttribute.swift b/Source/Model/CloudKitAttribute.swift index 805b0dc1..810eca7b 100644 --- a/Source/Model/CloudKitAttribute.swift +++ b/Source/Model/CloudKitAttribute.swift @@ -19,6 +19,7 @@ class CloudKitAttribute { let entityName: String let serviceAttributes: ServiceAttributeNames let context: NSManagedObjectContext + var notFoundRecordIDsForAttribute = [AttributeName: [RecordID]]() init(value: Any?, fieldName: String, entityName: String, serviceAttributes: ServiceAttributeNames, context: NSManagedObjectContext) { self.value = value @@ -56,7 +57,13 @@ class CloudKitAttribute { fetchRequest.includesPropertyValues = false let foundObject = try context.fetch(fetchRequest).first as? NSManagedObject - + + if foundObject == nil { + var values = notFoundRecordIDsForAttribute[fieldName] ?? [] + values.append(recordID.encodedString) + notFoundRecordIDsForAttribute[fieldName] = values + } + return foundObject }