swiftcore-dataxcode11nspersistentcloudkitcontainernsorderedset

How to set an ordered relationship with NSPersistentCloudKitContainer?


When I checked Used with CloudKit, the error Folder.children must not be ordered appeared. Any idea about an ordered relationship?

Using Xcode 11 (beta 3).

enter image description here

Here's the Folder Entity:

enter image description here


Solution

  • Applies to iOS 13, and early betas of iOS 14.

    Cloud kit can't use ordered relationships (which just blows, as ordered data is a fundamental Thing That Exists). The reason is that everything is backed with CKRecord (cloud kit records) not actual core data stuff — it's a completely different type of data storage, and the PersistentCloudKitContainer is doing on-the-fly rewrites of your data to CKRecords and back. CKRecords don't have mechanisms that work for maintaining journals of ordered items the way we need in Core Data ordered relationships.

    Which means it's not likely to be "fixed" anytime soon, as it would require changes to CloudKit and apple's iCloud generally (vs. just changes to CoreData).

    So...

    You have a few not-good choices:

    1. Don't have formal Relationships -- instead have a field where you store, e.g., a list of references. Maintain this yourself as you add/remove child objects. Essentially have an "admin" field in parent record that you use to maintain your own Relationship mechanism. (e.g.: generate a UUID for each child, parent stores a concatenated list of UUID's for all children)
    2. Use CoreData Relationships, and store ordering data in the child objects, e.g. orderIndex int field that you maintain manually.
    3. Mixed: use unordered Relationship from parent to children, and also store a field of "order of children" in parent. CoreData manages the relationship, and you can manually maintain and apply ordering of those children as needed.

    No matter what you do, it's bad:

    1. No relationships: no "fetch parent" and get references to children via relationship; you have to fetch both separately. Also no "delete" rules done for you, e.g. " on delete parent, cascade delete children." This works when you need children in more than one collection (as each collection record keeps it's own list of children)

    2. Ordering in child objects (this is what I use): you have to insert yourself in every add and delete operation on children, running code to adjust orderIndex values of all children. I manage this by having a CoreDataManager.createChild(atIndex:), CoreDataManager.deleteChild() and CoreDataManager.moveChild(toIndex:) functions that apply side effect of updating orderIndex values. But at least you get "fetch parent; for c in parent.children do..." and cascade delete's back. However: now child can only be in one parent's list, as child only has one orderIndex value.

    3. Relationship + manually maintained ordering field in parent: let core data manage the association, and when you need ordered children you can use a parent.orderedChildren() function that reads your .childOrder field of parent, applies it to .children list. But... you still have to manage the parent's .childOrder field manually, and change it every time you add/remove/reorder children. HOWEVER, with Cloud Sync, there are many potential bugs wit your .child list and .childOrder field value getting out of sync as children are added/removed in different app instances. Keeping the child list and the ordering separate means they can be updated by cloud sync separately.