Duplicating CoreData records with iCloud sync - ios

When core data syncs with iCloud, occasionally all of my records are doubled. I believe this happens on reinstall, where the app has not yet synced data, creates a new data set, and then iCloud syncs and there are double of each record for each model.
Is there a way to prevent this sort of behavior? Right now I'm checking every model on load and data sync for duplication, but this seems messy and hackish.

iCloud transfers data between devices asynchronously, and there can be quite some time between when you add data to the iCloud container, and when it actually gets transferred. Metadata is transferred between devices faster, but even this can be quite delayed.
The reason this is important is that you can never do a test on one device that will guarantee that data has not already been added to iCloud. One device may have seeded data, but not yet begun to upload its files/metadata. At that point, a second device cannot know about the seeded data from the first device.
Apple's advice is to handle this by de-deplicating your data after each merge. Each device just adds its own seed data, and if you find that it has been added twice, you delete half of it, being careful to ensure corresponding objects are deleted on each device.
This approach works, but feels a bit like a hack. An alternative, used in my Ensembles framework, is to supply global identifiers for your objects so that the sync framework can automatically import and merge seed data. That is the approach taken in Ensembles, and it makes seeding data quite a bit easier and less ad hoc.

Trying to use just an iCloud store is folly. Data must be stored both locally and in iCloud, or you will run into serious problems.
Use MagicalRecord if you want core data.

Related

Synchronising old data with NSPersistentCloudKitContainer

I'm using NSPersistentCloudKitContainer to synchronise data between different devices with CloudKit. It works perfectly well with a new project, however when I'm using it with old projects the old data which was added with NSPersistentContainer does not synchronise.
What I would like to achieve is to synchronise old data that was added with NSPersistentContainer after changing it to NSPersistentCloudKitContainer. Is it possible?
I've found a solution that works for my Core Data database - and mine is quite complex with multiple many-to-many relationships (A surgery/anaesthesia logbook app called Somnus)
I started by creating a new attribute for all my Core Data Entities called sentToCloud and setting it to FALSE by default in the Core Data model.
On the first load for an existing user:
Fetch request using the predicate "sentToCloud == FALSE" for each Entity type
Change sentToCloud to TRUE for each Object then save the MOC
This triggers NSPersistentCloudKitContainer to start syncing
I've done this in order of 'priority' that works for my database, assuming the iCloud sync sessions match the order in which Core Data is modified. In my testing this seems to be the case:
I first sync all child (or most child-like) Entities
Then sync their parents, and so on, up the tree
I sync the Object the user interacts with last, once everything else is in place so the relationships are intact and they don't think their data is borked while we wait for NSPersistentCloudKitContainer to reconnect all the relationships
I also leave any binary data (magically turned into a CKAsset behind-the-scenes) to last as it's not the most important part of my database
My database synced successfully from my iPad to iPhone and all relationships and binary data appear correct.
Now all I need is a way to tell the user when data is syncing (and/or some sort of progress) and for them to turn it off entirely.
ADDENDUM
So I tried this again, after resetting all data on the iCloud dashboard and deleting the apps on my iPhone & iPad.
Second time around it only synced some of the data. It seems like it still has a problem dealing with large sync requests (lots of .limitExceeded CKErrors in the console).
What's frustrating is that it's not clear if it's breaking up the requests to try again or not - I don't think it is. I've left it overnight and still no further syncing, only more .limitExceeded CKErrors.
Maybe this is why they don't want to sync existing data?
Personally, I think this is silly. Sometimes users will do a batch process on their data which would involve updating many thousands of Core Data objects in one action. If this is just going to get stuck with .limitExceeded CKErrors, NSPersistentCloudKitContainer isn't going to be a very good sync solution.
They need a better way of dealing with these errors (breaking up the requests into smaller requests), plus the ability to see what's going on (and perhaps present some UI to the user).
I really need this to work because as it stands, there is no way to synchronise many-to-many Core Data relationships using CloudKit.
I just hope that they're still working on this Class and improving it.

Sqlite with (CloudKit or iCloud Drive)

I have an iOS app that timestamps some events and loads them in UI. To persist the data I use CloudKit which is quite fine and fast enough for my use case.
1st problem is, when an event occures I save it instantly to cloudkit therefore if internet is not reachable I have to handle the situation. I had the idea of having a local sqlite db and sort of keep track of connectivity and deal with syncing manually. Here is where 2nd problem comes in in which any inconsistency of the data must be handled by me (e.g. if the user edits the data while offline).
I found YapDatabase library doing the exact things but I feel the learning curve is a little bit steep.
Another idea could be using sqlite db but saving the file in the icloud drive. This way it will be persisted automatically while I have access to the app sqlite. Any change in the db tables will change the file singnature and fire icloud drive sync.
What's the best choice?

CoreData and iCloud; how can I track which device last saved to the cloud?

I have a CoreData app (using https://github.com/lhunath/UbiquityStoreManager), backed by iCloud. In one use case a user with a local store enables iCloud (where data already exists). I want to prompt the user to make a decision of whether to migrate the local data to iCloud or just use the iCloud version. As part of this, I'd like to display the device name and last sync date of the version in iCloud.
I've been tinkering around with my NSPersistentStore's metadata, but that doesn't appear to get synced to iCloud.
Any suggestions?
You could use iCloud's key-value store to store the device name & date of the last sync.
My no doubt unpopular suggestion is "don't". Trying to determine what is in iCloud at any given time puts you on pretty shaky ground. You may be able to get it to work most of the time, but there will always be circumstances where it breaks down.
If you really must import some data when first enabling iCloud, I suggest just always importing the data, and then deduping later as the iCloud data comes in. As ugly as it sounds, that's the only approach really guaranteed to work with Apple's approach.
It is worth taking a look at other Core Data sync frameworks like TICDS and Ensembles. They take a more sane approach to data identity, which means you can avoid the whole deduping step. (Disclosure: I develop Ensembles)
do a metadata query on the iCloud files and check the most recent transaction log file in iCloud. See the link below for a sample app that uses this approach to check whether the app is properly synchronised with iCloud.
http://ossh.com.au/design-and-technology/software-development/sample-library-style-ios-core-data-app-with-icloud-integration/
EDIT:
I just realised I don't get the actual device name, but once you have found the most recent log file then use this to get the device. Just be aware this call may be expensive.
NSFileVersion *openedVersion = [NSFileVersion currentVersionOfItemAtURL:fileURL];
return openedVersion.localizedNameOfSavingComputer;

iOS, Core Data and iCloud - switching context

I am creating an app where data needs to be displayed right away from the local datastore. I fire off a background thread when the app starts to determine if iCloud is available.
I looked everywhere and can't find the solution to this: when iCloud becomes available, can I change the "options" on the persistentStore to start using iCloud transactions?
I'm not sure what the proper approach is in this situation. Everything I try causes the application to crash.
Originally I had it so the iCloud checking wasn't in a background thread and the app worked fine, but occasionally timed out.
You have not to know when iCloud becomes available. You just work with data but you don't send them directly to iCloud. iOS does it instead you. So only it knows when and how it should send data.
No, you can't change the options on an NSPersistentStore object once it exists. You can only specify the options when adding the persistent store to the NSPersistentStoreCoordinator. The closest you could get to changing options would be to tear down the entire Core Data stack and start over with different options.
That wouldn't help, though, because:
Even if you have detected that iCloud is available (I'm guessing using NSFileManager, either via its ubiquityIdentityToken or by calling URLForUbiquityContainerIdentifier:), your call to addPersistentStoreWithType:configuration:URL:options:error: might still block for a while. If there's new data available in iCloud, it doesn't start downloading until you add the persistent store, and that method doesn't return until the download process is finished. And sometimes, iCloud just makes that method block for a while for no readily apparent reason.
If you let the user make any changes to the data while using non-iCloud options, those changes will not get automatically sent to the cloud later. Core Data only sends changes to iCloud when the data changes while iCloud is active-- which makes it generate a transaction. You'd have to load and re-save any changes the user made, or those changes would never make it to the cloud.
You have, unfortunately, hit on one of the major stumbling points when using Core Data with iCloud. You can't make the full data store available until Core Data finishes communicating with iCloud-- because your call to add the persistent store doesn't return until then. And you can't do anything to speed up that process. This is just one of the headaches you'll run into if you continue trying to use iCloud with Core Data.
Depending on the nature of your data you might be able to use two data stores, one purely local and one synced via iCloud. You could make the purely local data store available while the iCloud one tries to get its act together well enough to be useful. If you stick with one data store though, you're stuck with the delay.

iCloud + Preloaded CoreData

I have database with some default content. How can I use iCloud to sync changes in database on different devices?
I know how sync devices if CoreData was empty first and I can migrate from external sqlite file to CoreData.
If you have an existing Core Data store and you want to add iCloud support, you'll need to transfer all of your existing data to a new data store and save the result. This is necessary because iCloud only generates transactions when you save changes-- so you need to effectively save changes for everything to jumpstart the syncing process.
In most cases you can do this in a single step by using NSPersistentStoreCoordinator's migratePersistentStore:toURL:options:withType:error:. Pass in the same iCloud options that you would use when calling addPersistentStoreWithType:configuration:URL:options:error:. Use this alternate approach only when moving from a non-iCloud data store to one that does use iCloud.
Also, beware of using iCloud with Core Data. It has not earned a reputation for reliability. Pay close attention to Apple's documentation and sample projects, and even then be prepared for it to just not work sometimes.

Resources