I'm considering using CloudKit in my app (it doesn't use Core Data) and have read as many materials as I can find. I haven't fully grasped it yet and have a basic question on CKRecord.Reference
. Does CloudKit guarantee CKRecord.Reference
value is always valid? By valid I mean the target CkRecord
pointed by the CKRecord.Reference
exists in the database.
Let's consider an example. Suppose there are two tables: Account
and Transaction
:
Account Table:
AccountNumber Currency Rate
------------- -------- ----
a1 USD 0.03
Transaction Table:
TransactionNumber AccountNumber Amount
----------------- ------------- ------
t1 a1 20
Now suppose user does the following:
- User first deletes account
a1
and its associated transactionst1
on device A. The device saves the change to cloud. - Then user adds a new transaction
t2
to accounta1
on device B, before the device receives the change made in step 1 from cloud. Sincea1
hasn't been deleted on device B, the operation should succeed locally. The device tries to save the change to cloud too.
My questions:
Q1) Will device B be able to save the change in step 2 to cloud?
I hope it would fail, because otherwise it would lead to inconsistent data. But I find the following in CKModifyRecordsOperation
doc (emphasis mine), which implies CloudKit allows invalid reference:
During a save operation, CloudKit requires that the target record of the parent reference, if set, exists in the database or is part of the same operation; all other reference fields are exempt from this requirement.
(BTW, I think the fact that, when using CloudKit, Core Data requires all relations must be optional also indicates that CloudKit can't guarantee relation is always valid, though I think that is mainly an issue on client side caused by data transfer size. The above example, however, is different in that it's an issue on cloud side - the data on cloud is inconsistent).
I also find the following in the document. However, I don't think it helps in the above example, because IIUC CloudKit can only detect conflict when the changes on the same record but the changes in step 1 and step 2 are on different records.
Because records can change between the time you fetch them and the time you save them, the save policy determines whether new changes overwrite existing changes. By default, the operation reports an error when there’s a newer version on the server.
If the above understanding is correct, however, I don't understand why the same document has the following requirement, which implies CloudKit doesn't allow invalid reference:
When creating two new records that have a reference between them, use the same operation to save both records at the same time.
Q2) Suppose CloudKit allows invalid reference on cloud side (that is, device B successfully saves the change in step 2 to cloud) , I wonder what's the best practice to deal with it?
I think the issue is different from the optional relation requirement in Core Data when using CloudKit, because in that case the data is consistent on cloud side and eventually the client will receive complete data. In the above example, however, the data on cloud is inconsistent so the client has to remedy it somehow (although client has little information helping it).
One approach I think of is to avoid the issue in the first place. My idea is to maintain a counter in the database and requires client to increase the counter (it's not Lamport clock. BTW, is it possible to use Lamport clock in this case?) when making any change. This should help CloudKit to detect conflict (though I can't think out a good strategy on how client should deal with it. A simple one is perhaps to prompt user to select one copy). However, this approach effectively uses cloud as a centralized server, which I suspect isn't the typical way how people use CloudKit, and it requires clients to maintain local counter value in various situations. I wonder what's the typical approach? Am I missing something?
Thanks for any help.
When saving a record that contains a CKReference
field, CloudKit verifies that the target record exists in the same record zone. If it doesn't, CloudKit reports an error and does not proceed the save.
In the case where you create two new records that have a reference between them, you use the same operation to save both records, and CloudKit creates them at the same time.
CloudKit does allow a CKReference
field to be nil
.
In the case you described, where a1
is not a new record, I'd expect that CKModifyRecordsOperation
returns you an error. Did you not get that?
Best,
——
Ziqiao Chen
Worldwide Developer Relations.