Proper handling of NSUbiquityKeyValueStore updates across devices? - objective-c

My app stores a single key-value pair in iCloud using NSUbiquityKeyValueStore, an array of objects. The entire array is saved to iCloud when a change is made to any object in the array. This works great as long as each device has an opportunity to pull down the latest update before a change is made locally. Otherwise the local change can get pushed up to iCloud before other devices' latest updates have been pulled down, and those updates get lost across all devices. Is this my app's shortcoming or iCloud's shortcoming, and how can I prevent this scenario from occurring?

Otherwise the local change can get pushed up to iCloud before other devices' latest updates have been pulled down
I ran into a similar issue this week with a project I'm working on. I just made sure that I didn't push anything up to the iCloud server until I received my first update from iCloud. Also, FWIW, I set a fake key-value pair right after initialization so that it updates immediately.
HackyStack's idea of a local flag is also a good solution; if a change comes in you can ask the user if they want to use it or not. (sorta like how Kindle asks if you want to update to the latest page).

I'm not sure I fully understand the exact issue, but I believe the answer is either a category on NSObject (where you could have a "version" property) to check the "version" of the object OR you need another key-value pair to store on iCloud for "version" that can be compared to one stored locally on the device (lastUpdateVersion) to know where you stand. If you could give me an exact real world example of your problem I could answer better... It could be that you don't even need a "version" but rather a flag (BOOL).

You should read the documentation for -[NSUbiquitousKeyValueStore synchronize]. It gives you a decent idea of when to use it and what its limits are. In particular, pay attention to the fact that it makes no promises on when it actually synchronises the data, and implies that updates are uploaded to iCloud only a couple of times at a minute, at most (and that may apply to the device as a whole, not just your app).
The key-value storage mechanism is intended to be very simple and used only for non-essential data, typically configuration information about your app. You shouldn't store user data in it, basically, or anything that resembles it. For that kind of data, use the file-based iCloud APIs. They're more complicated, but with them you have more insight into the sync state of your data, and most importantly you can be notified of conflicts and provide your own merge handler.

Is this my app's shortcoming or iCloud's shortcoming, and how can I prevent this scenario from occurring?
This is an app shortcoming and expected behaviour from iCloud. You can account for this in various ways, but in general, this won't be easy. Especially with >2 devices, there are scenarios where conflicting changes will never be presented to a device to do resolution, as generally speaking the iCloud behaviour is "last change wins" (see my longer description below). Some thoughts:
instead of using an array of objects, use individual keys for each object. Obviously this depends on the semantics of your app, but if the objects are essentially independent, then this generally will give your app the behaviour it expects 🎉
if all the items are interlinked, then you will have to do your own conflict resolution. The best way to do this will depend heavily on your app + data semantics. E.g. maybe you could add a timestamp to your array, or to some objects in the array. You could use new key names for every save so that all devices eventually get all keys and can resolve conflicts (obviously this could chew through storage quickly!). Resolving conflicts might not be worth doing depending what you're already storing locally to help with this
Background
I recently had reason to research the topic of NSUbiquitousKeyValueStore change conflicts in some (tedious) depth. I found some information in two old WWDC videos that expand on current Apple documentation, specifically WWDC11 Adopting iCloud Storage, part 1 (currently available here, found via here) at locations 17:38 and subsequently (e.g. 19:27). Another is a WWDC12 iCloud Storage Overview talk (here originally via here) at 6:30 and 10:55. I subsequently verified the behaviour described below by running two devices, an iPhone 8 running iOS 15.2 and an iPad Air 2 running iOS 12.4 with a test program and lots of console logging in Xcode. What follows is my best guess of the intended behaviour and mechanism for conflict resolution.
Summary
When an individual key is saved by a device using NSUbiquitousKeyValueStore.default.set(value, forKey: key), a hidden timestamp is included with the key with the device time of that call. If/when the operating system syncs with the iCloud replica of the key value store, it examines the timestamps for each key and, if the iCloud timestamp is earlier in time, it saves the new key value and timestamp into the iCloud key value store. If the key value is saved, devices that are currently registered and online to receive notifications will be notified that this key has changed and can fetch the new value if they wish. If iCloud does NOT save the key value, NO notification will happen on any device, and the change is simply dropped.
Notes
If all devices on this iCloud account are online while in use (caveat low power mode, poor internet connection etc.), the result is generally exactly what you want: the app makes a change, it is saved in iCloud, it propagates to other devices. Notifications happen as expected, if a device has registered for them.
If device A saves a value while it is offline, and another device B later saves a value while it is online, then device A goes online, the change from device A is ignored, as iCloud now has a newer value with a later timestamp. B will never be notified of A's change. However, if A has registered for changes, A will get notified of the newer B value and can then decide if it should re-submit its value.
Because of this "last in wins" behaviour, multiple values that belong together should thus be saved together as a dictionary or array, as suggested in various Apple docs and talks.
Values that don't interact should be saved as individual keys - thus allowing most recent changes from multiple devices to successfully intermingle.
There is no automated way to test these behaviours. Back in Xcode 9 days, it was possible to UI script two simulators to verify sync worked as expected, but that hasn't worked in a while, which leaves manual testing as a poor and tedious substitute.
NSUbiquitousKeyValueStore is a great solution for many scenarios beyond simple app settings. Personally, I'd like to see more keys (e.g. 10k instead of 1k), but the general ease of setup and separated storage from a customer's iCloud quota is generally a joy.
There's no perfect solution in a real world environment where devices are not always reliably connected. Indeed, some customers may intentionally keep, say, an older iPad, mostly offline to save battery between intermittent usage. If you can keep your synced data in small discrete units and save it one value per key, sync will generally work as expected.

Related

How to figure out that two apps are on the same device on macOS(Alternative of identifierForVendor in macOS)

advertisingIdentifier is different for apps from the same vendor.
Of course there is an ability to add apps into group and share some "unique string". But I suppose that there must be some easier way.
I also read about "Uniquely Identifying a Macintosh Computer" but I suppose that such apps are rejected in mac AppStore.
In our app we access the system serial number. We use it to try prevent multiple users using the same account + for debug purposes (so not for ads or anything, our app has none).
We also have code to access the hardware uuid but that code isn't actually used at the moment, but it is in there, so not sure how deep Apple checks. So you might be able to use this one too. As an additional step you could hash either of these (or hash them appended or something).
This app has been on the AppStore for a long while now, and was never rejected for this reason. So I'd say accessing this data on macOS should be ok (for now) depending on usage and safe to submit to the app store.
Keep in mind that in some rare cases, the serial number will not be available. In that case we store a random string in UserDefaults.standard and use that cached value in the future.
Since this information won't be available to your 'other' app(s), this workaround won't work for you though.

Core Data persistent store protection

I'm creating an app that relies quite heavily on Core Data. It is a content-driven app that primarily delivers question/answers to the user.
On its first load, the app delegate pulls through lots of data from an SQLite into the app's persistent store. The data is basically lots of content that is not only in-app purchasable, but is also copyright-protected.
Normally, developers requiring encryption/protection for Core Data need it for storing sensitive user-data. However, as in this (my) case, I would need to protect the persistent store from external access from anyone or any source (including the user), purely due to the fact that I don't want someone to be able to download the app's entire Intellectual Property from the persistent store.
I noticed on the iPhone Simulator that locating the persistent store and opening it (with an SQLite browser) was no trouble at all. This is a little worrying, and so, if this is also as easily possible for a release installation on a device, then I would like to know:
I don't necessarily want to go all-out on encryption, as I've found ways to do this row-by-row (lazily), so is there a quick way to obfuscate/scramble a persistent store?
This article shows how to encrypt individual attibutes(of course you can encrypt all attributes).

WinRT live tile on system startup

I have a live tile working which updates how many users are online and how many lobbies are open within the app. This begins updating when my app loses its visibility (no point it updating the live tile whilst the app is running), but I want it to update when I first turn the computer on.
I have had a look around and mentions of making the app a lock screen app have popped up but that is all, no explanation how to do it.
Does anyone know how to do this and provide a nice little explanation or link of how to do so?
Many thanks,
Kevin
You should use push notifications for this kind of behaviour. This msdn link has more info:-
Using tile notifications
Choosing the right notification method to update your tile
There are several mechanisms which can be used to update a live tile:
Local API calls
One-time scheduled notifications, using local content
Push notifications, sent from a cloud server
Periodic notifications, which pull information from a cloud server at a fixed time interval
The choice of which mechanism to use largely depends on the content you want to show and how frequently that content should be updated. The majority of apps will probably use a local API call to update the tile when the app is launched or the state changes within the app. This makes sure that the tile is up-to-date when it launches and exits. The choice of using local, push, scheduled, or polling notifications, alone or in some combination, completely depends upon the app. For example, a game can use local API calls to update the tile when a new high score is reached by the player. At the same time, that same game app could use push notifications to send that same user new high scores achieved by their friends.
You're right with the assumption that you require a lock screen capability to be able to run background tasks without your app being started once. The main process would be to extract the part of your application that gets the data into a background task that is probably triggered by a timer and write some code to be on the lock screen.
When I first encountered that restriction I was kind of surprised, but in terms of battery performance this design decision makes sense: Only consume battery power if the data is absolutely worth it. If it's worth, it is also of interest having it on the lock screen.
On MSDN is a good overview about lock screen along with further reading links. It's much better than what I could type in here. Come back with problems related the implementation (which actually even better fits the purpose of SO). This blog might be useful, too.

Perform a task every day even if running only in background

I need my application to perform a background task every day, but it does not comply with Apple's specification for running in background.
It's none of these: audio, location, VOIP, news stand, external-accessory—, Bluetooth-central.
I'm planning to use local notifications, but the point is, that if the user doesn't open the app (and just leaves it in the background), I cannot plan new local notifications.
What should I do in this situation?
Make something else.
Seriously, if your app doesn’t at least technically fit one of those categories, you’re not going to be able to get it to do background work in a way that’ll get you approved for the App Store.
I’ve seen apps that added specific functionality in one of those categories, orthogonal to their actual purpose, to be allowed to run in the background; for instance, there’s a couple of weather apps out there that play a continuous audio file—one of the available sounds being a silent one—in order to be able to update the lock screen’s “now playing” image with live data. If you don’t mind your users being unable to listen to any other music, and have your app continuously active (which might cause additional battery drain), that could work. But if you’re trying to have your app invisibly do things in the background, without interfering with anything else, in a way that’ll get you into the App Store, you’re probably out of luck.
Four options:
Go for jailbreak
Try to get in the app store as one of those types of apps, making up a feature that uses one of those types.
(my favorite) Really rethink your system, is it really necessary to schedule background tasks? Can't the user wait a bit when he opens the app? That loading can be performed in background. And can't you offload some of the work to a webserver? Are you collecting data? You shoudln't without the user consent.
Let your user know that it's important for him (he's the one using it right?) that he opens the app once a day.
I think I covered all yohr options except the one already covered by Noah.

Is an AssetURL guaranteed to be unique on a device?

Currently with iCloud, it is possible for a user with iTunes Match to swipe to delete a track, or download a new track from iCloud.
Is it guaranteed that the AssetURL of a track will not be used again on the same device?
I ask as im creating my own cache of the library meta data. When I detect a change to the library, rather than rebuilding my entire cache, id like to be able to just detect removed tracks and newly added ones, and update my cache accordingly.
It seems to be a large enough number to be unique on a device for most intents and purposes. If anyone can confirm this I will change the accepted answer.