iOS、CloudKit - 我的应用启动时是不是需要进行抓取?

Posted

技术标签:

【中文标题】iOS、CloudKit - 我的应用启动时是不是需要进行抓取?【英文标题】:iOS, CloudKit - do I need to do a fetch when my app starts?iOS、CloudKit - 我的应用启动时是否需要进行抓取? 【发布时间】:2017-03-04 18:23:06 【问题描述】:

我正在为 iCloud 更改通知设置注册。

假设将新设备添加到 icloud 帐户,我只是想知道该设备将如何获取私有数据库记录。

我需要一次性查询吗?

我希望在所有其他时间都会使用通知。

【问题讨论】:

【参考方案1】:

让我们从订阅通知的一些相关特征开始:

第一:订阅通知特定于用户+设备对。如果我在手机上安装你的应用程序,我就会开始收到通知。在我也安装应用程序之前,我不会在其他设备上收到通知。

第二:通知不可靠。苹果文档很清楚,他们不保证交付。当您收到通知时,可能已经有几个先前的通知。因此,Apple 提供了两种机制来跟踪您看到的通知:

    已读/未读状态:您可以将通知标记为已读。 Apple 的文档对这实际上做了什么自相矛盾。 This page 说

如果您使用 CKMarkNotificationsReadOperation 对象将一个或多个通知标记为已读,则不会返回这些通知,即使您为 previousServerChangeToken 指定 nil 也是如此。

然而,事实并非如此。获取操作清楚地返回已读和未读通知。 WWDC 2014 视频 231(高级 Cloudkit)与文档页面相矛盾,解释说始终返回未读令牌和已读令牌,因此多个设备可以同步。该视频给出了一个具体的例子,展示了这种行为的好处。这种行为也记录在 SO:CKFetchNotificationChangesOperation returning old notifications

    更改令牌:每个获取操作都会返回一个您可以缓存的更改令牌。如果您将令牌传递给 fetch,则 fetch 只会从该点返回令牌,无论是已读还是未读。

乍一看,Apple 似乎提供了您想要的行为:在一台设备上安装应用程序,开始处理通知,在第二台设备上安装应用程序,然后获取所有之前的通知以赶上进度.

不幸的是,正如我在CKFetchNotificationChangesOperation: why are READ notifications all nil? 中记录的那样,每当我获取通知时,之前标记为“已读”的通知都具有 nil 内容。已读通知中的所有信息都会丢失。

在我的场景中,我选择:

    始终在启动时获取最新记录 使用之前保存的更改令牌(如果存在)获取通知 处理新通知 将通知标记为已读 保存最新的更改令牌以供下次提取时使用

对于您的场景,您可以尝试:

    使用之前保存的更改令牌(如果存在)获取通知 处理通知(不要将它们标记为已读) 保存最新的更改令牌以供下次提取时使用

您的第一台设备将在每次后续提取时忽略旧通知,因为您从更改令牌点开始每次提取。您的第二台设备将在第一次执行时以零更改令牌开始,从而获取所有旧通知。

提醒一句:尽管前面提到的 WWDC 视频清楚地表明 Apple 保留了所有旧通知,但我没有找到说明他们保留这些信息多长时间的文档。可能永远,也可能不会。

更新了通知获取示例

以下是我获取通知、将其标记为已读以及缓存更改令牌的方式:

@property CKServerChangeToken *notificationServerChangeToken;

那么……

-(void)checkForUnreadNotifications

    //check for unread cloudkit messages
    CKFetchNotificationChangesOperation *op = [[CKFetchNotificationChangesOperation alloc] initWithPreviousServerChangeToken:_notificationServerChangeToken];

    op.notificationChangedBlock = ^(CKNotification *notification)
    
        //this fires for each received notification. Take action as needed.
    ;

    //maintain a pointer to the op. We will need to look at a property on the
    //op from within the completion block. Use __weak to prevent retain problems
    __weak CKFetchNotificationChangesOperation *operationLocal = op;

    op.fetchNotificationChangesCompletionBlock = ^(CKServerChangeToken *newServerChangeToken, NSError *opError)
    
        //this fires once, at the end, after all notifications have been returned.
        //this is where I mark the notifications as read, for example. I've
        //omitted that step because it probably doesn't fit your scenario.

        //update the change token so we know where we left off
        [self setNotificationServerChangeToken:newServerChangeToken]; 

        if (operationLocal.moreComing)
        
            //more notifications are waiting, recursively keep reading
            [self checkForUnreadNotifications];
            return;
        
    ;

    [[CKContainer defaultContainer] addOperation:op];

要从用户默认设置和检索缓存的更改令牌,我使用以下两个函数:

-(void)setNotificationServerChangeToken:(CKServerChangeToken *)newServerChangeToken


    //update the change token so we know where we left off
    _notificationServerChangeToken = newServerChangeToken;
    NSData *encodedServerChangeToken = [NSKeyedArchiver archivedDataWithRootObject:newServerChangeToken];
    NSUserDefaults *userSettings = [NSUserDefaults standardUserDefaults];
    [userSettings setObject:encodedServerChangeToken forKey:UD_KEY_NOTIFICATION_TOKEN_CKSERVERCHANGETOKEN_PROD];

    //Note, the development and production cloudkit environments have separate change tokens. Depending on your needs, you may need to save both.

还有……

-(void)getNotificationServerChangeToken

    NSUserDefaults *userSettings = [NSUserDefaults standardUserDefaults];
    NSData *encodedServerChangeToken = [userSettings objectForKey:UD_KEY_NOTIFICATION_TOKEN_CKSERVERCHANGETOKEN_PROD];
    _notificationServerChangeToken = [NSKeyedUnarchiver unarchiveObjectWithData:encodedServerChangeToken];    

【讨论】:

我将添加一个我的方法的示例。我将更改令牌存储在用户默认值中(编码/解码有点痛苦),以及我的获取操作。 是的,在初始设置时调用它以确保设备已“赶上”。您可以在didFinishLaunchingWithOptions 中调用它,但我更喜欢使用applicationDidBecomeActive,因为这还包括切换到应用程序/重新进入应用程序,以及重新启动应用程序。我还在didReceiveRemoteNotification 中调用它,因此始终使用相同的代码将设备带到最新的通知。 是的,没错。您可以将该代码放入op.notificationChangedBlock 的完成处理程序中。然后,您确定每个通知都会运行相同的评估,即使是那些错过发送推送通知的通知。您最终有 3 种方式来触发通知处理:启动应用程序 (applicationDidBecomeActive)、恢复应用程序 (applicationDidBecomeActive) 和实际接收推送通知 (didReceiveRemoteNotification)。然后所有 3 个都将使用该操作来检查所有待处理的通知。 实际上,有 4 种方式...因为您在实例化类时也会调用它。 让我们continue this discussion in chat。

以上是关于iOS、CloudKit - 我的应用启动时是不是需要进行抓取?的主要内容,如果未能解决你的问题,请参考以下文章

CloudKit - 无法从 iCloud 检索数据

CloudKit iOS:用户无法删除应用程序数据

CloudKit 通知未发送到所有设备

如何从 CloudKit 更新所有客户端记录?

CloudKit:当应用程序暂停时,CKOperations 不运行

当应用程序处于后台模式时,如何获得错过的 CloudKit 通知?