Core Data - iCloud behavior - ios

I am implementing iCloud support in my core data app using an NSManagedDocument as the core data stack.
Everything seems to be working fine but with some unexpected behavior:
With the universal app installed on my iPhone and iPad:
an entity added from the iPhone gets iClouded to the iPad;
an entity added from the iPad gets iClouded to the iPhone;
the entity added from the iPhone, deleted on the iPhone is automatically deleted on the iPad
the entity added from the iPad, deleted on the iPad is automatically deleted on the iPhone
However ...
the entity added from the iPhone, deleted on the iPad is not deleted on the iPhone
the entity added from the iPad, deleted on the iPhone is not deleted on the iPad
Is this the expected behavior? This is not how the Apple 'notes' app behaves on my iPhone and iPad. Any changes to any notes are reflected across all devices.
If this is not the correct iCloud behavior, any ideas where I might have gone astray?
Thanks so much.

Is this the expected behavior?
That depends on how you define "expected". It's not how Apple intends it to work, but you're not the first person to run into this problem. In practice, yeah, it's expected at least some of the time.
A couple of things you should check:
Does this condition persist even if you quit and re-launch the app? It could be that the instance is getting deleted but that your app isn't updating its UI properly. That would make it appear that the object was still there, until you relaunched the app.
Take a look at the "did import" notification you receive from iCloud. It should contain object IDs for inserted, updated, and deleted instances. Make sure it correctly indicates which object(s) have been deleted on the receiving end.
Check out your object handling to make sure you're not somehow holding on to the object that should have been deleted, or re-creating it.
But keep in mind, if you're using Core Data with iCloud, you are asking for trouble and you may not be able to fix this. Core Data's iCloud integration is still not all that reliable. Expect problems, potentially too severe to fix.

Related

Failing to receive NSUbiquitousKeyValueStoreInitialSyncChange

I have a iPhone/iPad App written in Objective-C.
My App shares data between multiple devices via the iCloud using the Key-Value Storage data model. This logic has all worked now for literally years.
Yesterday, I cleared my App from my iPhone A, I reinstalled the App onto iPhone A and then I enabled iCloud Sharing on iPhone A.
After enabling iCloud data, as expected, a full copy of my most recent data downloaded onto iPhone A from the iCloud. Everything looked good.
But later, when I modified the data on iPhone A and attempted to share it with the other devices via iCloud, I got an error message.
The error message warned me that until I had received an InitialSyncChange notification from iCloud, it was unwise for me to propagate the data on iPhone A through the iCloud and onto my other devices.
Long ago, when I wrote this iCloud code, I had set it up so that it would refuse to send data out to the iCloud unless it had previously set a flag indicating that it had seen an incoming InitialSyncChange notification.
But what puzzled me here was that I had seen a copy of my most recent shared data downloaded onto iPhone A from the iCloud after I had enabled iCloud sharing.
When I looked into this and repeated the situation several times, I discovered that when the initial copies of my shared iCloud data were arriving on iPhone A, they were arriving with StoreServerChange notifications and not with InitialSyncChange notifications.
I have no idea why this has changed. Has anyone else seen something similar?
I have an obvious and easy fix for the problem but, in truth, I really hate applying fixes when I don't fully understand why things have changed.
My fix, if you are curious, would be to change how I set the flag that records if I've seen an incoming InitialSyncChange notification.
Now I will set it when I've seen either a InitialSyncChange notification or StoreServerChange notification.
Either notification 'proves' that I've got a valid copy of the shared data. And if as the flag is not set, then the existing logic prevents the App from sending out bogus data and messing with the valid shared data.
Any thoughts or trouble-shooting advice would be appreciated.

How to get default project with NSPersistentCloudKitContainer up and running?

I followed the same steps as in "Using Core Data With CloudKit":
New project
Enable Core Data + Cloud Kit
Add iCloud/CloudKit entitlement + Background mode/remote notifications entitlement.
In the iPhone Xr simulator I signed into an iCloud account I created (and then verified on icloud.com!) and ran the app, creating multiple entries.
I then signed into the same iCloud account in the iPhone Xs simulator. I ran the app but no entries were merged. Creating entries in this simulator also does not merge back over to the Xr simulator.
What am I missing?
To see the changes with Simulators you have to quit the app, and reload it (or build & run).
Simulators have never been able to receive Remote Notifications to trigger an iCloud sync so you need to manually force a sync, but I've found that syncing cannot be triggered manually from the menu in my Xcode 11 beta (gives an error).
There is a good post by Andrew Bancroft about some other things such as setting the automaticallyMergesChangesFromParent property to true, but this doesn't make a difference when using Simulators (EDIT: It does, but I didn't realise as I was just building & running each time).
Andrew's Post: https://www.andrewcbancroft.com/blog/ios-development/data-persistence/getting-started-with-nspersistentcloudkitcontainer/#where-s-my-data
I'm in the same boat as I can't afford to install any beta software (except Xcode) so I'm going to have to stick with the simulators. But my experience of converting an existing App to CloudKit has been very, very positive. I just had to do three things to my existing project:
Add Background Notification & CloudKit capabilities
Make sure all Core Data attributes & relationships are optional (or have a default value if nil)
Rename NSPersistentContainer to NSPersistentCloudKitContainer
...and that's it! Mind blown.
All my nested many-to-many relationships appear to work perfectly.
I still need to work out how to sync images currently stored as JPGs in the users Documents directory but I suspect they'll need to be stored in Core Data as BLOBs to enable conversion to CKAssets in the background.
Apple now have example code called CoreDataCloudKitDemo.
This includes all the basic stuff and also has additional code which processes the changes when they arrive from the other device.
You need to have the lines
description.cloudKitContainerOptions =
NSPersistentCloudKitContainerOptions(
containerIdentifier: "iCloud.com.developerid.databasename")
to get your local database going to iCloud and creating the schema.
You need to use the Cloudkit Dashboard on the web to see the schema etc.

How to resolve CloudKit functionality inconsistency between devices

I've integrated CloudKit into my iOS app and I'm encountering functionality inconsistency between devices.
One of my users has an iPod 5th Gen and an iPhone 6. If they use the iPod everything works as expected. However if they use the iPhone 6 they can only receive data and notifications, they can't make changes of their own. When the iPhone attempts to make changes to the iCloud public database I receive no errors and changes are made locally, they just never make it to the server (everything appears to work, it just doesn't).
Given that the user can use the iPod successfully but the same iCloud account only receives data on the iPhone suggests to me that this might be a permissions or settings issue.
I've checked:
User is logged into iCloud
iCloud drive is on
The app appears enabled in the iCloud Drive menu in settings
The app appears enabled under the "Look Me Up By Email" menu
Other details:
The app is available through TestFlight
The app is using the production container (required for TestFlight)
I don't know what code to supply because the app functions as expected on other devices but let me know what could help. Any help would be greatly appreciated.
This was resolved by changing my modify operations qualityOfService property to .UserInitiated.
After I reviewed the completion handler for my CKModifyRecordsOperation I realized that I was saving data necessary for local functionality regardless of whether the completion handler received an error or not (and incidentally, whether it was executed or not). So when it appeared to me that my operation was being run and returning no errors, it actually wasn't being run at all. Changing the qualityOfService appears to have resolved this.
Answer that led to the solution

Questions concerning iCloud + core data

I have an app on the app store, that uses coredata as storage. I wan't to update the app with iCloud synchronization as new feature. Following apple`s sample code, I managed to have my core data storage synchronize between devices.
However, I'm experiencing problems when either iCloud synchronization is turned off/on in the app on only one of the devices, or when the app is deleted from the device and the reinstalled. In both cases, data is not synchronized back to the device, although it is available just fine on a second device (which was not disabled/reinstalled).
I also found that all storage is effectively erased completely, when I delete the app from all devices, and then reinstall. Althrough I get a couple of merge notifications in the console (even some without errors), I can't see no data in the local storage of the device.
Browsing the mobile documents folders on my mac still reveals lots of transaction logs in the icloud storage of my app.
Even deleting the app from all devices and starting from scratch wont sort things out. I will end up in a situation where data is either only synced to one device, or not synced at all.
I wonder if there is anything I can do about this inconsistent state that is created when only one device is temporarily iCloud disabled, or the app is deleted from ONE device?
As for my code, its an 1:1 copy of the recipces example from apple.
Daniel Pasco talked about using Core Data and iCloud together at NSConference 2012. Some notes from that blog post:
launching with -com.apple.coredata.ubiquity.logLevel 3 to get a spamfest in the message log saying what Core Data and iCloud are doing.
The conclusion from this talk appears to be that using Core Data and iCloud are really not ready for each other at this stage.
He posted an updated Core Data Recipes project on Github which may or may not fix your problem.
Apple makes it seem easy, but there are a number of nuances with regard to correctly seeding iCloud with data, and what happens afterwards when iCloud support is toggled on and off on different devices.
I implemented a sample project that demonstrates a straightforward way to add iCloud support to Library-style CoreData apps. It's called iCloudStoreManager and it's available on github.
I'm still testing it before I add iCloud support to one of my own production apps. It's working, but I see unexpected errors and delays when an iPad 3 is in the mix. It works, but with long delays.
I've also tested with iPhone 4, iPhone 4S, and the original iPad, and any mix of those devices works well in my experience.

iOS 4.3.2 user reported app crashing on launch

An update of my app has just been approved by Apple and users are now complaining the app does not launch anymore. It also happens to some new users.
I have absolutely no idea where the problem is nor can I reproduce the problem. I have tested the update on various devices (& simulator) before submitting the update: iPhone 2G running 3.1.3, iPod Touch 2G running 4.3 , iPhone 3G and iPhone 4 running 4.3.1. They ALL work as expected. The update has a few new features like random picking photos from user's photo library using AssetsLibrary framework, I have weak-linked the framework to support iOS 3 and the feature does not load until selected by the user so it should not be the problem. After all, the update has been tested and approved by Apple.
I have difficulty collecting crash information from users with the problem, but I know one of them uses iPhone 4 with iOS 4.3.2. A quick google search reveals that iOS 4.3.2 has problems launching third party apps, I suspect my problem has something to do with this but I can not confirm it. I am planning to downgrade my dev iPhone 4 to iOS 4.3.2 to test it.
Does anybody here experienced similar problem? My app's ranking has dropped significantly because of the negative reviews so I need to fix this as soon as possible.
Edit:
There should not be any watch dog problem, I tested the update on the above mentioned devices with and without Xcode/debugger.
Memory management. I can not reproduce the problem (I tried quite hard) so I can not confirm if it's EXC_BAD_ACCESS, I did check reference count and nil released objects (safely release) when applicable, I am absolutely not a pro in memory management so I take it seriously, I checked leaks and allocations with instruments, stress-tested and did memory warning simulations, no problem was found.
I have UIApplicationWillEnterForegroundNotification in -loadview, it's only available after iOS 4.0 so I check if it exists with & operator because I use it.
I do not persist data other than saving facebook connect token and expiry date (NSDate) in NSUserDefaults, since the problem also happens to new users so I think it's something else
We're going to need more info, unfortunately. But just off the top of my head:
Watch dog? What sorts of stuff are you loading when you launch your app? It may be that resources are constrained on the devices having this issue and you are doing work that should be done a separate thread, or otherwise delayed until after the app has launched.
EXC_BAD_ACCESS. There could be a race condition going on that is resulting in most people able to launch OK, but for some it just isn't working because of bad references. I know, you write good code and manage your references like a pro, but sometimes a non-obvious slip-up can creep in.
Are you safely instantiating some types of classes? An example that bit me once was with the MFMailComposeViewController class. Before instantiating you're suppose to call its static method canSendMail. If a user hasn't setup any mail accounts on their device (hard to figure there would be anyone that fits this scenario, but hey! found out after an update that there are quite a few!) then the app would crash.
What data persistence (if any) do you have? Are you using Core Data? Serialized objects in a plist? NSUserDefaults? Your strategy may be corrupting data you are persisting and that is leading to a crash.

Resources