iCloud NSUbiquitousKeyValueStore初始同步/访问延迟 - 如何处理?

20

我正在使用NSUbiquitousKeyValueStore存储一些应用程序设置。我的逻辑是:在保存数据到本地时,同时将其保存到NSUbiquitousKeyValueStore作为备份。需要设置时,我首先从本地读取,如果本地没有找到数据(例如,应用程序重新安装后),我才会使用iCloud键值存储。如果用户有多个共享一个iCloud ID的设备,他可以在一个设备上编写设置,并将它们下载到另一个设备上(我会警告他关于覆盖现有数据)。

我有一个奇怪的问题。步骤:

  1. 安装应用程序并将其数据保存到NSUbiquitousKeyValueStore。确保数据存在。
  2. 删除应用程序(假定数据仍然保留在iCloud中)。
  3. 等待几分钟,然后从Xcode内部安装和启动应用程序。
  4. 尝试使用[[NSUbiquitousKeyValueStore defaultStore] dataForKey:@"mykeyname"]读取设置键 - 有时可以,但有时找不到键!
  5. 等待15秒,然后再次尝试。成功了。困惑了。

因此,似乎iOS需要一些时间才能使远程键值存储对于dataForKey:调用在本地可用。 如果我写过这样的系统(实际上我做过-在另一生命中的某个时候),显然在请求和接收键值数据之前必须有一定的延迟。因此,我希望有一些通知说:“我们在第一次启动时完成了下载/同步键值存储”或类似的东西。

据我所了解,我可以在主线程同步地使用NSUbiquitousKeyValueStore(这对我很方便)。但是[[NSFileManager defaultManager] URLForUbiquityContainerIdentifier:nil]会返回有效的URL,然后我会得到“未找到密钥”。所以我不能依赖它。有没有办法确保NSUbiquitousKeyValueStore正在工作并已下载?特别是在网络缓慢的情况下这很重要。

更新

添加[[NSUbiquitousKeyValueStore defaultStore] synchronize](如苹果文档中所述)到init和load有所帮助。仍然有关于iCloud的许多问题。

昨天我成功将数据保存到手机1的键值存储中,并在手机2上还原。 今天我删除了手机2上的应用程序并尝试还原数据。但是,即使[[NSFileManager defaultManager] URLForUbiquityContainerIdentifier:nil]返回有效的URL并调用[[NSUbiquitousKeyValueStore defaultStore] synchronize],在调用dataForKey:MY_DATA_KEY时,我也会得到nil。

当我尝试从iCloud上的手机1还原数据时(应用程序仍然安装),它成功了,但是当我重新安装此应用程序时,恢复不再成功。

临时解决方案是:“关闭iCloud->文档和数据-关闭并打开网络-打开文档和数据”,但是您还应等待几分钟,然后它应该正常工作。

所以问题是:

  1. 你是否也遇到了iCloud的这些问题?
  2. 有没有办法找出数据是不可用还是还未下载?
  3. iCloud是否存在已知的“延迟”?我听说过7秒,但显然不是真的。
  4. 似乎当应用程序未被卸载时,iCloud数据的更新非常快(几秒钟),但当重新安装应用程序时,icloud需要几分钟才能实际化键值存储。有没有办法强制执行此过程?

P.S. 以下是我的CloudHelper供您参考-一个相当简单的c ++类,可用于将二进制数据写入/从iCloud键值存储中读取。它无法编译,我已经适应了SO,使其更清晰-删除了我的引擎相关代码。除了之前提到的问题外,如果您删除MySystem :: ... 调用,它运行得很好。

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;
}

更新 2

现在是2016年。Android已经成为iOS的邪恶孪生兄弟,人类发现了引力波,希格斯获得了他的诺贝尔奖,微软买下并扼杀了诺基亚。但iCloud仍然像以前一样愚蠢。

最终,我在多个VPS上构建了自己的网络服务堆栈。我拒绝使用第三方服务,因为它们大多不稳定且难以预测。但我需要iCloud。因为苹果的另一个失败之作SecKeyChain不起作用。当我的游戏开始时,其服务就会死掉。因此,我决定在云中存储随机UUID以区分用户(不再有设备ID),即使重新安装后也能保留这些信息。但是,什么可能会出错呢?一切都可能!我花了两天时间让这个愚蠢的东西在没有错误的情况下部署,现在却时不时地丢失我的数据!

谢谢你,Apple,谢谢,谢谢,谢谢!啦啦啦!万岁!(音乐声逐渐消失为哭泣声)


一直遇到完全相同的问题。这是有问题的,因为用户期望在启动应用程序后立即看到他们无处不在的数据,而不是几分钟后。你是否提交了错误报告? - samvermette
1
我有完全相同的问题。这真的很烦人。似乎已经有人报告了这个错误。请查看此问题https://dev59.com/f2bWa4cB1Zd3GeqPTyzf - erkanyildiz
有人找到解决方案了吗?我也遇到同样的问题。用户启动应用程序,我进行同步,从存储中获取数据,但读取数据时它为空。这必须在他们第一次启动时工作,以便我可以查看云中是否有其他数据可供导入。 - skinsfan00atg
3个回答

4

结论

临时解决方案是: - 在从键值存储获取数据之前调用同步 - 为确保它能够工作,“关闭iCloud->文档和数据 - 关闭并重新打开网络 - 打开文档和数据”,但在iCloud下载所有所需数据之前,您还应等待几分钟

注意:当应用程序安装并已经使用过键值存储保存/加载数据时,iCloud数据的更新非常快(7-15秒),但是当您重新安装应用程序时,似乎需要几分钟时间才能实际化键值存储。

我很乐意听取您的想法,因为iCloud看起来几乎是不可用的功能。但我不想设置自己的服务器仅仅为了获得相同的功能。


1
重新安装应用程序时的延迟非常有用,谢谢。 - lewis

1
我在应用程序启动时将一个虚拟密钥设置为NSUbiquitousKeyValueStore,并调用同步。这个结果并不是100%的解决方案,但有些改善。你可以尝试一下。

-1

显然,当应用程序等待缓慢的网络时,它不应该挂起。这都在iCloud设计指南中有所说明。

注册NSUbiquitousKeyValueStoreDidChangeExternallyNotification,调用-synchronize,希望最终会收到通知。

如果数据已经是最新的,我认为您不会收到通知,并且我认为没有简单的方法可以知道数据的年龄。


2
是的,我知道。但正如我在自己的答案中提到的那样,在重新安装应用程序时它是无用的。通知永远不会到达(实际上可能会在第二天到达,我没有等那么久),即使您尝试手动获取数据,除非您以某种方式刷新icloud缓存(关闭-打开icloud文档和数据,关闭-打开飞行模式),否则您将得不到任何东西。 - Tertium

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接