I am using iCloud to store sync user preferences between devices. On the device, these are stored in an array of 'Favorite Teams' in NSUserDefaults, and I am using MKiCloudSync to mirror them to the NSUbiquitousKeyValueStore. Changes on one device are propagating to the second device well.
But I am not sure how to prevent the cloud data from being wiped on the first launch after a new install. Here is what's happening:
Device A launches for the first time. App finds nothing in the cloud. User adds multiple items to the array in NSUserDefaults. Changes are synced immediately to cloud.
Device B launches for the first time, but is offline. User adds a single item to the NSUserDefaults array, then remembers the app supports iCloud, so finds some wifi instead.
Device B pushes its version of the defaults to the cloud (with only one item). Device A pulls it, effectively wiping out all of the teams added on Device A.
Is this a limitation of iCloud or is my implementation naive? The docs address a similar issue where a 'highest level' is synced, and adds application logic to never overwrite this value with a smaller value. That's fine when there is some clear business logic to adhere to (higher level is always the one to keep), but when data is more arbitrary, I don't see how I can determine what to do.
Or is it because I am using an array in NSUserDefaults for 'Favorite Teams' and replacing it wholesale? If I used separate keys for each team, perhaps they will be synced independently, based on time code?
Any time you sync a value for a specific key, you run the risk that it will be changed by a different device using the same account. The iCloud service chooses the winning value for you, makes updates, and notifies you when it's done. This is a limitation of iCloud and of your app, and is a simple example of why syncing is hard. What if your step 2 above looked like this:
Device B launches for the first time, and iCloud is available. The app downloads the current data from device A. The user changes their mind and deletes all the data they created on device A. Then they a single new item.
Well, what then? Step 3 still happens exactly as you describe it, except that this time the incoming data is what the user wants. You could refactor your data but the same kind of situation will still be possible.
One option is to keep a non-syncing local copy of the data, so that you can compare incoming changes with the previous local state. What to do when they're different is up to you. Just don't forget that even dramatic changes might well be exactly what the user wants, and not a syncing issue that needs to be fixed. Or, they might be something that would lose data the user wants to keep. Resolving this conflict is your job.
Related
I am wondering whether it is possible to determine at which time my local Core Data store was synchronized with iCloud. From iCloud is trivial. You can just take the time of the last NSPersistentStoreDidImportUbiquitousContentChangesNotification. However, I could not find any method to check when my local changes were completely transmitted to iCloud.
Any ideas?
You can't find this out. The only information available is the transaction logs, but those don't tell you when the data was actually synced. Transaction logs are created when you save changes. At some later time (probably soon, but there's no guarantee) they get synced to the iCloud service. You don't get notified of this.
It might be possible to infer sync timing via out-of-band communication from other devices. For example, when you receive the did-import notification, write the current time to NSUbiquitousKeyValueStore. Then monitor that key to see when changes are received at the other end. That would at least tell you that some changes had been received by some other device. At best though, it would notify you of when changes had been downloaded at the other end, not when they had been successfully uploaded to the iCloud service.
Just after some advice and recommendations here if I may.
I'm creating an iPad app (IOS6) that will write data to the local database on the device and then either straight away or later on replicate that record to a web service (so a Cloud service basically).
What is the best way to go about this you think?
I was thinking of just having a column in the local DB called "synced" and set the flag to '0' right away when the record is created, then sync records with a '0' either right away or during regular intervals. Then obviously set the flag to '1' when each record is replicated.
I want the app to work offline and then sync when the device has an available connection to my web service.
Ideally every record should be replicated right away or seconds later, but in the event of no network connectivity I want to be able to queue the replication to occur.
So what's the best way or achieving this you think? Thanks in advance :)
The solution is going to depend a lot on how complex your total solution is.
For example, if the records are only being created on the local device and then uploaded, without ever being modified, then your solution will be more than adequate.
However, if you allow update of the records on the local device or the records can be updated once they get into your web service, then you need to start managing conflict resolution. The way that we address this situation is to record a timestamp in the "master" database (the one updated by the web service) and synchronize that timestamp when a record is uploaded either as a new record or as an update. When the user updates a record, we send the timestamp and if the value in the database is different than the sent database, the update request is rejected. Of course there are different approaches to this conflict resolution, this is just one that works for our application and users.
I'm syncing a list of table view items via iCloud. When would be best to perform the sync. Here is my understanding of the various options.
didFinishLaunchingWithOptions - this will only get called when the app is launched. As most users send the app to background, rather than quit, it is likely this method won't be called very often.
applicationWillEnterForeground - this will happend every time the app is opened from a background state, if the internet connection is slow, this could cause a pause is the UI displaying?
applicationDidEnterBackground - I believe we only have 5 seconds to perform any actions, so a slow connection might mean we can't sync.
What are your thoughts best time to sync?
Well, besides the fact that a document saves every so odd amount of seconds, the best time to sync with iCloud is very dependent on the circumstance.
For example, if you have created a brand-new object that would be lost if not stored to iCloud, it's probably a good idea to do a sync with iCloud right away.
On the contrary, if you have created a brand-new object that wouldn't be lost if not stored to iCloud due to it being saved within Core Data, then maybe you can combine the save into one elsewhere given that you're concerned about the speed and CPU that the sync will take up.
I have two processes that are talking to the same persistent store. I save the context on one process, and I post a distributed notification. The other process sees the distributed notification, and fetches its data again, but still receives the old data. Is there some kind of "flushing" I need to do to get the other process to get the correct data from the store?
EDIT: So, it turns out that I was flushing the data correctly. NSManagedObjects have a "refreshObject:mergeChanges" method that you use to do this. The issue appears to be timing related. Let's say I have two processes, A and B. Process A is the main process and does a save to the database. Then Process B does a save to the database and sends a notification to Process A that it has done so, and Process A fetches the new data. I've found that if Process A's save and Process B's save are too close together, the old data is fetched by Process A even if I refresh. If I force there to be some time between the two saves, then it works out correctly.
Obviously this seems like some kind of race condition, where perhaps the notification is getting sent before the data is actually getting saved to the database, however, the notification gets sent in the didSave method of the managed object, at which point the context has already saved.
You should check the merge policy concept, to manage, get and communicate the correct values of a persistent store coordinator between different contexts.
Here -> http://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/CoreData/Articles/cdChangeManagement.html#//apple_ref/doc/uid/TP30001201-CJBDBHCB
That should resolve the problem.
Hope this can help.
I have a launchd daemon that every so often uploads some data via a web service using NSOperationQueue.
I need to be able to persist this data, both so that it can later be re-uploaded in the event of failure, even between sessions (in case of computer shut down, for example).
This is not a high load application, it probably receives items intermittently no more than 1 or 2 every minute, often with several hour gaps in between.
My initial implementation without this persistence in place is as follows:
Daemon receives data.
Daemon parses data into an object of type MyDataObject.
Daemon creates instance of NSOperation subclass with MyDataObject as the object to upload and adds it to its NSOperationQueue.
NSOperationQueue goes through and uploads MyDataObject via web service as it is able.
This part all functions just fine. The part I now want to add is the persistence in case of web service failure, computer shut down, etc.
It seems like I could use an NSMutableArray of MyDataObjects along with NSKeyed(Un)archiver containing all the items which had not yet been uploaded and observation of the -isFinished key of all the operations to remove items from the array, but it seems like there should be a simpler way to do is, with less room for things to go wrong, especially as far as thread safety goes.
Can somebody point me in the right direction?
You could add two operations per item. The first would store the item to local storage, and the second would depend on the first and would remove the item from local storage on success.
Then, when you want to restore any items from local storage, you create only the store-to-the-cloud operations, not the store-locally operations. As before, they remove the items from local storage only if they succeed, and if they don't succeed, they leave the items in local storage for the next attempt.