I encountered issues with some branch objects being assigned to multiple zones error (on iOS 17.5.1 and above). I get the errors when calling persistentContainer.shareshare(:to:completion:) and persistentContainer.persistUpdatedShare(:in:completion:).
"The operation couldn't be completed. Request '89D3F62D-548D-4816-9F1B-594390BD8F70' was aborted because the mirroring delegate never successfully initialized due to error: Error Domain=NSCocoaErrorDomain Code=134060 "A Core Data error occurred." UserInfo={NSLocalizedFailureReason=Object graph corruption detected. Objects related to 'Oxa2255fdc1fa980c5 <x-coredata://CB800FA2-6054-4D91-8EBC-E9E31890344F/CDChildObject/p588>' are assigned to multiple zones: {l <CKRecordZonelD: 0x3026a1170; zoneName=com.apple.coredata.cloud-kit.share.5D30F204-5970-489F- BC2E-F863F1808A93, ownerName=defaultOwner>, <CKRecordZonelD: 0x302687b40; zoneName=com.apple.coredata.cloud-kit.zone, ownerName=_defaultOwner>"
In my setup, I moved all my root objects into one custom zone (there is only one custom zone in my private database). In one of my root object, there are 6 'one-to-one' and 2 'one-to-many' relationships. The branch objects can contains other relationships.
Create root object flow:
func saveToPersistent(_ object: ViewModelObject) {
serialQueue.async {
let context = backgroundContext()
context.performAndWait {
// Create new baby with its one-to-one child objects.
let cdNewBaby = self.newCDBaby(object, context)
if let share = self.getShareZone(.privateStore).first {
self.moveToShareZone(pObjects, share: share, store: .privateStore)
}
CoreDataManager.single.saveContext(context)
self.updateZoneNSaveContext([cdNewBaby], context: context)
} // context.perform
} // serialQueue.async
}
func backgroundContext() -> NSManagedObjectContext {
let context = persistentContainer.newBackgroundContext()
context.transactionAuthor = contextAuthor
context.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy
return context
}
func getShareZone(_ storeType: StoreType, zoneName: String? = nil) -> [CKShare] {
var shares: [CKShare] = []
do {
shares = try persistentContainer.fetchShares(in: stores[storeType])
} catch {
print(error)
return shares
}
if let zoneName = zoneName {
shares = shares({ $0.recordID.zoneID.zoneName == zoneName })
}
return shares
}
func moveToShareZone(_ sharedObjects: [NSManagedObject], share: CKShare, store: StoreType) {
self.persistentContainer.share(sharedObjects, to: share) { managedObjects, share, container, error in
if let error = error {
print(error)
} else if let share = share, let store = self.stores[store] {
self.persistentContainer.persistUpdatedShare(share, in: store) { (share, error) in
if let error = error {
print(error)
}
}
}
}
} // moveToShareZone
Create one-to-many relationship branch object flow:
serialQueue.async {
let context = self.backgroundContext()
context.performAndWait {
// MARK: Retrieve the Root record
let pObjects = CDRootRecord.fetchRecord(rootRecord.uuidString, store: store, zoneName: zoneName, context: context)
if let pRootRecord = pObjects.first {
self.newCDLogContent(pRootRecord.self, viewModelObject: viewModelObject, context: context)
// MARK: Save Log
CoreDataManager.single.saveContext(context)
}
} // context.performAndWait
} // serialQueue
Questions:
(1) Should I save a root object first before share to custom zone; or share to custom zone first before save? (I implemented save before share to zone in the past and found some issues on iOS16 where the object is not saved; and end of sharing object before save which works)
(2) As I understand, if a branch record is saved under a root record, it should automatically go into the root record. Or do I have to also share the branch record to the custom zone?