cloudkitbackreferenceremote-notifications

How to Retrieve the CKRecord from the CKSubscriptionID that came from fetchAllSubscriptionsWithCompletionHandler?


and thank you for your advice!

I am using fetchAllSubscriptionsWithCompletionHandler, and I do see the subscritionID from each CKSubscription after the push notification is sent. How do I retrieve the CKRecord from the subscriptionID?

I do not see the Remote Push Notification from a CKReference that was created. I can see the CKRecord and its related record via CloudKit DashBoard. I do receive a Push Notification from when its parent record is created, but not when the CKReference is created on the child record.

-(void)SubscribeForReference:(CKRecord *)record
{

NSUserDefaults *trackSubscription = [NSUserDefaults standardUserDefaults];
BOOL hasSubscribed = [[NSUserDefaults standardUserDefaults] objectForKey:@"alreadySubscribedForReference"] != nil;

 //BOOL hasSubscribed = [trackSubscription  objectForKey:@"alreadySubscribedForReference"];

if (hasSubscribed == false) {
    //creates a subscription based on a CKReference between two ckrecords

    NSPredicate *predicate = [NSPredicate predicateWithFormat:@"sentences == %@", record.recordID];
    // 1) subscribe to record creations
    CKSubscription *subscriptionRelation =
    [[CKSubscription alloc] initWithRecordType:@"RecordTypeName"
                                     predicate:predicate
                                       options:CKSubscriptionOptionsFiresOnRecordCreation | CKSubscriptionOptionsFiresOnRecordUpdate | CKSubscriptionOptionsFiresOnRecordDeletion | CKSubscriptionOptionsFiresOnRecordUpdate];

    //http://stackoverflow.com/questions/27371588/cloudkit-notifications

    CKNotificationInfo *notificationInfo = [[CKNotificationInfo alloc] init];
    // I added this because of apple's documentation 
    notificationInfo.desiredKeys = @[@"word",@"identifier"];
    notificationInfo.alertLocalizationArgs = @[@"word"];  
    notificationInfo.alertLocalizationKey = @"%1$@";
    notificationInfo.shouldBadge = YES;

    subscriptionRelation.notificationInfo = notificationInfo;
    [self.privateDatabase saveSubscription:subscriptionRelation completionHandler:^(CKSubscription * _Nullable subscription, NSError * _Nullable error) {
        if (error == nil) {
            [trackSubscription setObject:subscription.subscriptionID forKey:@"alreadySubscribedForReference"];
            [trackSubscription synchronize];
        }else
            NSLog(@"something went wrong with saving the CKSubcription with error...\n%@\n",[error localizedDescription]);
    }];

}
else{
           NSLog(@"\nSubscribeForReference: ALREADY has subscription: %@   set for key 'alreadySubscribedForReference' \n\n ", [trackSubscription objectForKey:@"alreadySubscribedForReference"]);

}

}

The code below is ran when the app is launched, provided there is an Internet connection:

     -(void)runWhenAppStarts
      {
        CKFetchSubscriptionsOperation *fetchSubscriptionsOperation =   [CKFetchSubscriptionsOperation fetchAllSubscriptionsOperation];
        fetchSubscriptionsOperation.fetchSubscriptionCompletionBlock = ^(NSDictionary *subscriptionsBySubscriptionID, NSError *operationError) {
        if (operationError != nil)
        {     
        // error in fetching our subscription
        CloudKitErrorLog(__LINE__, NSStringFromSelector(_cmd), operationError);

        if (operationError.code == CKErrorNotAuthenticated)
        {
            // try again after 3 seconds if we don't have a retry hint
            //
            NSNumber *retryAfter = operationError.userInfo[CKErrorRetryAfterKey] ? : @3;
            NSLog(@"Error: %@. Recoverable, retry after %@ seconds", [operationError description], retryAfter);
            dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(retryAfter.intValue * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
                [self subscribe];
            });
        }
    }
    else
    {
        if (self.subscribed == NO)
        {
            // our user defaults says we haven't subscribed yet
            //
            if (subscriptionsBySubscriptionID != nil && subscriptionsBySubscriptionID.count > 0)
            {
                // we already have our one CKSubscription registered with the server that we didn't know about
                // (not kept track in our NSUserDefaults) from a past app install perhaps,
                //
                NSLog(@"\nsubscriptionsBySubscriptionID (dictionary) = %@\n",subscriptionsBySubscriptionID);
                NSArray *allSubscriptionIDKeys = [subscriptionsBySubscriptionID allKeys];
                NSLog(@"\nallSubscriptionIDKeys (array) = %@\n",allSubscriptionIDKeys);

                if (allSubscriptionIDKeys != nil)
                {
                    [self updateUserDefaults:allSubscriptionIDKeys[0]];
                    for (NSString *subscriptions in allSubscriptionIDKeys) {
                        NSLog(@"subscriptionID: %@\n",subscriptions);
                    }
                }
            }
            else
            {
                // no subscriptions found on the server, so subscribe
                NSLog(@"...starting subscriptions on server...\n");
                [self startSubscriptions];
            }
        }
        else
        {
            // our user defaults says we have already subscribed, so check if the subscription ID matches ours
            //
            NSLog(@"...our user defaults says we have already subscribed, with subscriptionsBySubscriptionID = %@\nso check if the subscription ID matches the one already stored in NSUserDefaults...\n",subscriptionsBySubscriptionID);

            if (subscriptionsBySubscriptionID != nil && subscriptionsBySubscriptionID.count > 0)
            {
                // we already have our one CKSubscription registered with the server that
                // we didn't know about (not kept track in our NSUserDefaults) from a past app install perhaps,
                //
                //NSDictionary *subscriptionsBySubscriptionID has a structure of @{key: value} == @{NSString: CKSubscription}

                NSArray *allSubscriptionIDKeys = [subscriptionsBySubscriptionID allKeys];//contains the NSString representation of the subscriptionID.
                NSArray *allSubscriptionIDVALUES = [subscriptionsBySubscriptionID allValues];// the values are the corresponding CKSubscription objects

                for (CKSubscription *values in allSubscriptionIDVALUES) {
                    NSLog(@"\nCKSubscriptionValue = %@\n",values);

                }

                NSLog(@"\n...we already have our one CKSubscription registered with the server that..so lets look at allSubscriptionIDKeys =%@.\n\nvalues ...\nallSubscriptionIDVALUES = %@\n\n",allSubscriptionIDKeys,allSubscriptionIDVALUES);

                if (allSubscriptionIDKeys != nil)
                {
                    NSString *ourSubscriptionID = [[NSUserDefaults standardUserDefaults] objectForKey:kSubscriptionIDKey];
                    if (![allSubscriptionIDKeys[0] isEqualToString:ourSubscriptionID])
                    {
                        // our subscription ID doesn't match what is on the server, to update our to match
                        NSLog(@"...our subscription ID doesn't match what is on the server, going to update our  NSUserDefaults...\n");

                        [self updateUserDefaults:allSubscriptionIDKeys[0]];
                    }
                    else
                    {
                        // they match, no more work here
                        NSLog(@"...iCloud server already has this subscriptionID, so do nothing.i.e. don't subscribe again..\n");
                    }
                }
            }
        }
    }
};
[self.privateDatabase addOperation:fetchSubscriptionsOperation];
}

Solution

  • When you fetch subscriptions, you don't get a specific record ID. Instead, the subscription's .recordType will tell you the type of record this subscription monitors. Typically, if you have 1,000 users, then you would have 1,000 instances of the record, and each user would create subs to monitor when their instance is modified.

    When the subscription is triggered, you'll receive a notification. You can use CKFetchNotificationChangesOperation to retrieve the notifications. Each notif includes a .recordID value that tells you specifically which record changed and caused the subscription to fire.

    You're currently querying the subs to make sure the user has properly subscribed. Next, you also need to query the notifications with CKFetchNotificationChangesOperation to see which records have been updated when the subscription(s) fired.