From 2ded589ec1cd7d442d50e54d67206c627d65d3ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oleg=20Mu=CC=88ller?= Date: Wed, 4 Apr 2018 22:53:51 +0200 Subject: [PATCH 1/4] adding fixes for fixing relationships --- .../Classes/Fetch/FetchAndSaveOperation.swift | 45 +++++++++++++++++++ .../RecordToCoreDataOperation.swift | 9 +++- Source/Model/CloudKitAttribute.swift | 10 ++++- 3 files changed, 61 insertions(+), 3 deletions(-) diff --git a/Source/Classes/Fetch/FetchAndSaveOperation.swift b/Source/Classes/Fetch/FetchAndSaveOperation.swift index 50995fc2..1ecb3e65 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,47 @@ 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 } + + // TODO: move to extension + 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 { + // TODO: show warning? + print("NOT FOUND " + recordId) + } + } catch { + // TODO: handle error + print("ERROR: \(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 8440723d..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 @@ -54,10 +55,15 @@ class CloudKitAttribute { fetchRequest.predicate = NSPredicate(format: serviceAttributes.recordID + " == %@" , recordID.encodedString) fetchRequest.fetchLimit = 1 fetchRequest.includesPropertyValues = false - fetchRequest.includesSubentities = 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 } From 765a956d188cd20767b24e64475b651b2586cf5f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oleg=20Mu=CC=88ller?= Date: Thu, 5 Apr 2018 19:12:49 +0200 Subject: [PATCH 2/4] error handling --- Source/Classes/Fetch/FetchAndSaveOperation.swift | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/Source/Classes/Fetch/FetchAndSaveOperation.swift b/Source/Classes/Fetch/FetchAndSaveOperation.swift index 1ecb3e65..a4c9f6ef 100644 --- a/Source/Classes/Fetch/FetchAndSaveOperation.swift +++ b/Source/Classes/Fetch/FetchAndSaveOperation.swift @@ -123,12 +123,10 @@ public class FetchAndSaveOperation: Operation { object.setValue(foundObject, forKey: attributeName) } } else { - // TODO: show warning? - print("NOT FOUND " + recordId) + print("warning: object not found " + recordId) } } catch { - // TODO: handle error - print("ERROR: \(error)") + self.errorBlock?(error) } } } From a2d34f6acb70d36434c059dea943b07ffab5da79 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oleg=20Mu=CC=88ller?= Date: Thu, 5 Apr 2018 19:22:14 +0200 Subject: [PATCH 3/4] back to disabling includesSubentities --- Source/Classes/Fetch/FetchAndSaveOperation.swift | 2 +- Source/Model/CloudKitAttribute.swift | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/Source/Classes/Fetch/FetchAndSaveOperation.swift b/Source/Classes/Fetch/FetchAndSaveOperation.swift index a4c9f6ef..7639a840 100644 --- a/Source/Classes/Fetch/FetchAndSaveOperation.swift +++ b/Source/Classes/Fetch/FetchAndSaveOperation.swift @@ -105,11 +105,11 @@ public class FetchAndSaveOperation: Operation { for recordId in recordIDs { guard let relationship = object.entity.relationshipsByName[attributeName], let targetEntityName = relationship.destinationEntity?.name else { continue } - // TODO: move to extension let fetchRequest = NSFetchRequest(entityName: targetEntityName) fetchRequest.predicate = NSPredicate(format: serviceAttributes.recordID + " == %@" , recordId) fetchRequest.fetchLimit = 1 fetchRequest.includesPropertyValues = false + fetchRequest.includesSubentities = false do { let foundObject = try context.fetch(fetchRequest).first as? NSManagedObject diff --git a/Source/Model/CloudKitAttribute.swift b/Source/Model/CloudKitAttribute.swift index 810eca7b..4e7c393d 100644 --- a/Source/Model/CloudKitAttribute.swift +++ b/Source/Model/CloudKitAttribute.swift @@ -55,6 +55,7 @@ class CloudKitAttribute { fetchRequest.predicate = NSPredicate(format: serviceAttributes.recordID + " == %@" , recordID.encodedString) fetchRequest.fetchLimit = 1 fetchRequest.includesPropertyValues = false + fetchRequest.includesSubentities = false let foundObject = try context.fetch(fetchRequest).first as? NSManagedObject From 1c6c20d69d4ad0deced8da5fb53b9added7b33ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oleg=20Mu=CC=88ller?= Date: Mon, 9 Apr 2018 22:10:26 +0200 Subject: [PATCH 4/4] =?UTF-8?q?removing=20=E2=80=9E.includesSubentities=20?= =?UTF-8?q?=3D=20false=E2=80=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Source/Classes/Fetch/FetchAndSaveOperation.swift | 1 - 1 file changed, 1 deletion(-) diff --git a/Source/Classes/Fetch/FetchAndSaveOperation.swift b/Source/Classes/Fetch/FetchAndSaveOperation.swift index 7639a840..332b3e63 100644 --- a/Source/Classes/Fetch/FetchAndSaveOperation.swift +++ b/Source/Classes/Fetch/FetchAndSaveOperation.swift @@ -109,7 +109,6 @@ public class FetchAndSaveOperation: Operation { fetchRequest.predicate = NSPredicate(format: serviceAttributes.recordID + " == %@" , recordId) fetchRequest.fetchLimit = 1 fetchRequest.includesPropertyValues = false - fetchRequest.includesSubentities = false do { let foundObject = try context.fetch(fetchRequest).first as? NSManagedObject