I'm using NSUbiquitousKeyValueStore to store some app settings. My logic is: when I save data locally, I save it to NSUbiquitousKeyValueStore also as a backup. When I need settings, I read locally and I only use iCloud key-value store if no data is found locally (after app is reinstalled, for example). If user has several devices sharing one icloud id, he can write settings on one device and download them to another (I warn him about rewrite).
I have a strange issue. Steps:
So it seems like ios needs some time to make remote key-value storage for my app available locally for dataForKey: call. If I'd wrote such a system (actually I did - some time ago, in another life) there obviously must be a delay before asking and receiving a key-value data. So I'd like to have some notification saying: "we finished downloading/syncing key-value storage on first start" or something similar.
As far as I understand I can work with NSUbiquitousKeyValueStore in main thread synchronously (which is convenient for me). But [[NSFileManager defaultManager] URLForUbiquityContainerIdentifier:nil] returns a valid url, and then I get "key isn't found". So I can't rely on it. Is there a way to be sure NSUbiquitousKeyValueStore works an is downloaded? It's important especially with slow internet.
Adding [[NSUbiquitousKeyValueStore defaultStore] synchronize] (as written in apple docs) to init and load was helped a little. Still there are many questions to iCloud.
Yesterday I've successfully saved data to the key-value store on phone 1 and restored on phone 2. Today I've deleted app on phone 2 and tried to restore the data. But even [[NSFileManager defaultManager] URLForUbiquityContainerIdentifier:nil] returned valid URL and I called [[NSUbiquitousKeyValueStore defaultStore] synchronize] I get nil when call dataForKey: MY_DATA_KEY.
When I tried to restore data from icloud on phone 1 (app is still installed) it succeeds, but when I reinstalled on this phone the app restore doesn't succeed any more.
Temporary solution is: "turn off iCloud->Documents&Data - turn off and on network - turn on Documents&Data", but also you should wait several minutes, and then it should work.
So, questions:
P.S. Below is my CloudHelper for your reference - pretty simple c++ class to write/read binary data to/from iCloud key-value store. It is not compilable, I've adapted it for SO somewhat to make more clear - removed my engine related code. Still if you remove MySystem::... calls it works pretty well. Except that I mentioned before.
class CloudHelper
{
public:
static bool init();
static void deInit();
//save our data to iCloud with
static int saveData(unsigned char* data, int from, int count);
//get our data from iCloud
static unsigned char * loadData(int *retsize, int * retint);
//does iCloud work for us
static bool isEnabled();
//do we have our key in iCloud
static int isAvailable();
static const int RESULT_OK = 0;
static const int RESULT_NO_CONNECTION = 1;
static const int RESULT_NOT_FOUND = 2;
static const int RESULT_SYNC_ERROR = 3;
private:
static bool enabled;
static NSURL *ubiq;
};
bool CloudHelper::enabled = false;
NSURL *CloudHelper::ubiq = NULL;
#define MY_DATA_KEY @"my_data_key"
int CloudHelper::saveData(unsigned char* data, int from, int count)
{
if ([NSUbiquitousKeyValueStore defaultStore])
{
NSData *d = [[[NSData alloc] initWithBytes:(data + from) length:count] autorelease];
[[NSUbiquitousKeyValueStore defaultStore] setData:d forKey: MY_DATA_KEY)];
if ([[NSUbiquitousKeyValueStore defaultStore] synchronize] != TRUE)
return RESULT_SYNC_ERROR;
return RESULT_OK;
}
return RESULT_NO_CONNECTION;
}
unsigned char * CloudHelper::loadData(int *retsize, int * retint)
{
if ([NSUbiquitousKeyValueStore defaultStore])
{
[[NSUbiquitousKeyValueStore defaultStore] synchronize];
NSData *d = [[NSUbiquitousKeyValueStore defaultStore] dataForKey: MY_DATA_KEY];
if (d != NULL)
{
if (retsize != NULL)
*retsize = d.length;
if (retint != NULL)
*retint = RESULT_OK;
return d.bytes;
}
else
{
if (retsize != NULL)
*retsize = -1;
if (retint != NULL)
*retint = RESULT_NOT_FOUND;
}
}
else
{
if (retsize != NULL)
*retsize = -1;
if (retint != NULL)
*retint = RESULT_NO_CONNECTION;
}
return NULL;
}
int CloudHelper::isAvailable()
{
int result = RESULT_NO_CONNECTION;
if ([NSUbiquitousKeyValueStore defaultStore])
{
[[NSUbiquitousKeyValueStore defaultStore] synchronize];
NSData *d = [[NSUbiquitousKeyValueStore defaultStore] dataForKey: MY_DATA_KEY];
if (d != NULL)
result = RESULT_OK;
else
result = RESULT_NOT_FOUND;
}
else
result = RESULT_NO_CONNECTION;
return result;
}
void CloudHelper::deInit()
{
enabled = false;
[ubiq release];
}
bool CloudHelper::init()
{
enabled = false;
NSURL *ubiq_ = [[NSFileManager defaultManager] URLForUbiquityContainerIdentifier:nil];
[[NSUbiquitousKeyValueStore defaultStore] synchronize];
if (ubiq)
{
enabled = true;
ubiq = [ubiq_ retain]; //save for further use
}
else
{
//is implemented elsewhere: this writes a local file with a counter, and if it is < REMINDER_COUNT allows us to show a warning to users
bool allow = MySystem::isAllowToShowDialog();
if (allow)
{
//determines network state with Apple's Reachability
if (!MySystem::isNetworkAvailable())
MySystem::showMessageBox(@"Network error"); //No network
else
MySystem::showMessageBox(@"You should log into your iCloud account to be able to backup your settings."); //No login
}
}
return enabled;
}
UPDATE 2
It's 2016. Android has become ios's evil twin, the humanity has discovered gravitational waves, Higgs have received his nobel, Microsoft has bought and killed Nokia. But iCloud is still as stupid as it was.
Finally I've made my own stack of network services on several VPS. I refused to use third-party services, because most of them are unstable and unpredictable. And yet I need iCloud. Because another die-born child of apple does not work. SecKeyChain. Its service dies when my game starts. So I decided to store random UUID in cloud to distinguish users (there is no device id anymore) even after reinstall. But what could go wrong? Everything! I've spend two days to make this stupid s*it to deploy without errors, and now it loses my data from time to time!
Thank you Apple, thank, thank, thank! La-la-la! Hip-hip hooray! (sounds of circus music, fading into weeping)
Conclusion
Temporary solution is: - call synchronize before get data from key-value store - to be sure it would work "turn off iCloud->Documents&Data - turn off and again on network - turn on Documents&Data", but also you should wait several minutes before iCloud downloads all needed data
Note: when app is installed and already worked (saved/loaded) with key-value store updates of iCloud data are pretty fast (7-15 sec), but when you reinstall the app it seems that icloud needs several minutes to actualize key-value store.
I'd be glad to hear your thoughts, because icloud looks like almost unusable feature. But I don't want to set up my own server to merely get the same functionality.
I am setting one dummy key to NSUbiquitousKeyValueStore and calling synchronize on app launch. The result is not 100% solution but somewhat better. You can try this.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With