I am working on CloudKit sync in my app ("Tiny data, all devices" model, with a custom zone in the private database).
CKModifyRecordsOperation contains clientChangeTokenData property of NSData type which is described in the docs as follows:
When you modify records from a fetch operation, specify a client-generated data token using this property to indicate which version of the record you last modified. Compare the data token you supplied to the data token in the next record fetch to confirm the server has successfully received the device’s last modify request.
I don't get why I should bother given that with each request, I get a completion block which tells me whether the server has successfully received my request. Why do I need to manually compare this client token?
Is specifying clientChangeTokenData required to handle my use case correctly? I track local data changes and push everything in the queue on each data change. Remote changes are tracked via zone subscription.
If it is required, how do I generate this token correctly given that I have all kinds of record changes in my CKModifyRecordsOperation (my API usage aims for batch operations). What is the general workflow here?
Thank you.
It's unclear from the docs so I'd guess the clientChangeTokenData is useful in the case of sending up a large modify records operation, e.g. deleting 100 records. Then say your app sends a fetch request in another operation with a query (or fetch changes) result set that would be affected by the modifications which either:
is started and is running concurrently to the existing modify operation which hasn't finished yet.
is started before the server has finished processing the results of the previous modifications (the docs tend to allude to a processing delay).
If the fetch completion contains a different clientChangeTokenData to the one sent with the modify then you know it hasn't received (or finished processing?) the changes yet. In this situation you could either error, with an alert to say the server needs more time, or automatically retry the fetch after some time.
By the way in my tests, this token is per-device.
You would only have a reason to check the token if you had local changes that you want to write to CloudKit and you want to make sure that your changes are based on the latest version of the data in CloudKit.
You could also just ignore the token and save the data anyway. If the data has changed in the mean time, you will get a CloudKit error and you could handle it then.
Related
So, super weird use case.
Basically, a client created objects and syncs them to the server. The server persists them, and returns that same object with a UID. When the client gets that UID object, it deletes the client version and saves the server version.
I’m worried that the client will send the object, and while the server is validating, disconnect. Then when the client sends the object again, we have duplicates.
I could generate a client ID to avoid his situation and persist that with the server object, but I was looking into a way to only persist objects if the client successfully receives the response, so we know it won’t resend the request
I googled around, but I couldn’t find anything. Is there a way to do this?
So, as I thought, my answer really demonstrates a lack of understanding on how HTTP works. I suspected that it wasn't possible with this technology - and it isn't - but there's really an underlying problem that I should have addressed.
The correct answer is to have an id generated on the client that is also stored in the database. The reason is because this makes the request idempotent - that is, the client can resend the same request as many times as it likes without messing up the server.
Whenever the server gets a request to make a new object, it simply checks our client ids sent. If that object already exists, don't make it again, just return the server generated object. Simple!
As described in https://developer.apple.com/reference/cloudkit/ckserverchangetoken, the CloudKit servers return a change token as part of the CKFetchRecordZoneChangesOperation callback response. For what set of subsequent record fetches should I include the given change token in my fetch calls?
only fetches to the zone we fetched from?
or would it apply to any fetches to the db that that zone is in? or perhaps the whole container that the db is in?
what about app extensions? (App extensions have the same iCloud user as the main app, but have a different "user" as returned by fetchUserRecordIDWithCompletionHandler:, at least in my testing) Would it be appropriate to supply a change token from the main app in a fetch call from, say, a Messages app extension? I assume not, but would love to have a documented official answer.
I, too, found the scope of CKServerChangeToken a little unclear. However, after reviewing the documentation, both CKFetchDatabaseChangesOperation and CKFetchRecordZoneChangesOperation provide and manage their own server change tokens.
This is particularly useful if you decide to follow the CloudKit workflow Dave Browning outlines in his 2017 WWDC talk when fetching changes (around the 8 minute mark).
The recommended approach is to:
1) Fetch changes for a database using CKFetchDatabaseChangesOperation. Upon receiving the updated token via changeTokenUpdatedBlock, persist this locally. This token is 'scoped' to either the private or shared CKDatabase the operation was added to. The public database doesn't offer change tokens.
2) If you receive zone IDs via the recordZoneWithIDChangedBlock in the previous operation, this indicates there are zones which have changes you can fetch with CKFetchRecordZoneChangesOperation. This operation takes in it's own unique server change token via it's rather cumbersome initializer parameter: CKFetchRecordZoneChangesOperation.ZoneConfiguration. This is 'scoped' to this particular CKRecordZone. So, again, when receiving an updated token via recordZoneChangeTokensUpdatedBlock, it needs persisting locally (perhaps with a key which relates to it's CKRecordZone.ID).
The benefit here is that it probably minimises the number of network calls. Fetching database changes first prevents making calls for each record zone if the database doesn't report any changed zone ids.
Here's a code sample from the CloudKit team which runs through this workflow. Admittedly a few of the APIs have since changed and the comments don't explicitly make it clear the 'scope' of the server change tokens.
Our app supports offline activity. Meaning we want to persist locally the creation of new core data objects as well as any modifications on existing objects. Then when the app goes online again we automatically push those changes (and any dependencies) up to the server.
I would think that RestKit would support such an operation, but currently when offline we store creations/modifications in a local cache. If I kill the app, those changes are not persisted. And also there is no attempt by RestKit to post those items to their originally intended endpoints.
I cannot find any documentation to support what we need here.
Is there a way for RestKit to do what we need?
If not, how do I get offline changes to persist to the disk (and not cache)? Then would it be appropriate to flag those as not uploaded to server, and then try uploading them when we are back online?
Any other important things I should consider?
At the time of writing RestKit does not support that feature.
To save to disk you need to call saveToPersistentStore: instead of just save: on the MOC.
You need to implement a scheme yourself, observing the 'online' status of the app and scanning the data store for things that need to be uploaded (which means maintaining a flag to indicate if it's happened yet).
I solved this issue by adding another field called 'updated' to my object. This field is set to true or 1 when the object is created or modified. Each time the application is started or synchronized, it iterates through the local core data copy and sends the objects with 'updated' set. On the web service, the response ALWAYS clears 'updated' to false when returning a response. This works well in the case where the web service and app are both online.
I am using RestKit for an iOS To app. I already had done following using restkit:
1. Pull server objects from rest api in json format.
2. Delete orphan objects in core data which are no longer present on server.
Now i have to build the following scenario, if the internet is available on the device and user is adding a new data item,then what should i do first i.e should i store the new data first locally and then post to server or first i post the data to server and the pull it back on device ?
Secondly if the internet is not available on device and user inserts a new data item then saves data locally, On internet availability how do i post newly added data items to the server i.e what approach should i follow and if restkit can help me tackling this scenario ?
RestKit includes reachability monitoring (actually part of AFNetworking). So you can set a block to be run when the status changes:
[objectManager.HTTPClient setReachabilityStatusChangeBlock:...
Generally, store the item locally in all cases. When the item has been pushed to the server, set the sync date or a flag on the item to confirm that it has been updated.
This is really a broader question about how you manage local modifications and updates to the server. You may want an overall scheme to list the dirty objects and push updates to the server and have the server response set the sync time for each item. If you use 2 dates (one for the last local modification and one for the remote sync) then a quick predicate fetch on the model will tell you which objects are dirty and need to be pushed to the server.
I have a model which sends a HTTP request to an external web service on creation in order to find out some information to add before it is saved.
Currently I'm doing this in a before_create callback. I recently learned that before/after callbacks happen within database transactions.
Am I opening myself up to any issues such as limiting DB throughput by doing this? Would it be better to commit the record before sending the http request and then update the record when it returns?
As long a s you keep a transaction open, all the locks it acquired are active. If you have a call to an external source that may stall you for a long period of time, be sure not to to have any unrelated locks in the same transaction.
In other words: don't put anything else into the same transaction.
If you don't mind the new row being visible before you look up the additional information, you might just commit and later update the row.
Or you fetch the information from the external web service before you even start the transaction. That would be cleanest / fastest solution for the database.
PostgreSQL lock types.
How to view locks.