iOS, CloudKit - do I need to do a fetch when my app starts?

泪湿孤枕 提交于 2019-12-01 05:08:59

问题


I'm setting up registrations for notifications of iCloud changes.

Say a new device is added to the icloud account, I'm just wondering how that device will get the private database records.

Do I need to do a one off query?

I'm hoping that notifications will be used at all other times.


回答1:


Let's start with some relevant characteristics of subscription notifications:

First: Subscription Notifications are specific to a user + device pair. If I install you app on my phone, I start getting notifications. I won't get the notifications on another device until I install the app there, too.

Second: Notifications are unreliable. Apple docs are quite clear that they do not guarantee delivery. When you receive a notification, there could have been several prior notifications. Thus, Apple offer's two mechanisms to track which notifications you've seen:

  1. Read/unread status: you can mark notifs as read. Apple's docs contradict themselves about what this actually does. This page says

If you mark one or more notifications as read using a CKMarkNotificationsReadOperation object, those notifications are not returned, even if you specify nil for previousServerChangeToken.

However, this isn't true. The fetch operation clearly returns both read and unread notifications. WWDC 2014 Video 231 (Advanced Cloudkit) contradicts the documentation page, explaining that unread tokens are always returned as well as read tokens so multiple devices can sync up. The video gives a specific example that shows the benefits of this behavior. This behavior is also documented on SO: CKFetchNotificationChangesOperation returning old notifications

  1. change token: each fetch operation will return a change token that you can cache. If you pass the token to a fetch, the fetch will only return tokens from that point, whether read or unread.

At first glance, it would seem that Apple is providing for the behavior you want: install the app on one device, start processing notifications, install the app on a second device, and fetch all those prior notifications in order to catch up.

Unfortunately, as I've documented in CKFetchNotificationChangesOperation: why are READ notifications all nil?, any time I fetch notifications, the ones previously marked as "read" all have nil contents. All the info in the read notifications is lost.

In my scenario, I chose to:

  1. Always fetch the latest record(s) at startup
  2. Fetch notifications using the previously saved change token (if it exists)
  3. Process the new notifications
  4. Mark the notifications as read
  5. save the latest change token for use on the next fetch

For your scenario, you could try:

  1. Fetch notifications using the previously saved change token (if it exists)
  2. process the notifications (DO NOT MARK THEM AS READ)
  3. save the latest change token for use on the next fetch

Your first device will ignore old notifications on each subsequent fetch because you're starting each fetch from the change token point. Your second device will start with a nil change token on the first execution, and thus pick up all of the old notifications.

One word of caution: even though aforementioned WWDC video clearly says Apple keeps all the old notifications, I have found no documentation that says how long they hold this info. It may be forever, it may not.

updated with notification fetch example

Here's how I'm fetching notifications, marking them read, and caching the change token:

@property CKServerChangeToken *notificationServerChangeToken;

Then...

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

To set and retrieve the cached change token from the user defaults, I use the following two functions:

-(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.
}

and...

-(void)getNotificationServerChangeToken
{
    NSUserDefaults *userSettings = [NSUserDefaults standardUserDefaults];
    NSData *encodedServerChangeToken = [userSettings objectForKey:UD_KEY_NOTIFICATION_TOKEN_CKSERVERCHANGETOKEN_PROD];
    _notificationServerChangeToken = [NSKeyedUnarchiver unarchiveObjectWithData:encodedServerChangeToken];    
}


来源:https://stackoverflow.com/questions/42599643/ios-cloudkit-do-i-need-to-do-a-fetch-when-my-app-starts

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!