I have a record that I share from device A to device B. Device B accepts the share and show the info on Device B's UI. Device B subscribes to Database changes on that shared record using a CKDatabaseSubscription.
I then delete that record on device A which then triggers a notification to Device B about a database change. I then handle that notification on Device B by fetching the DB changes using a CKFetchDatabaseChangesOperation. I then subsequently get the Zone changes using a CKFetchRecordZoneChangesOperation.
I get calls to recordZoneFetchCompletionBlock and fetchRecordZoneChangesCompletionBlock but that is it. Neither recordChangedBlock nor recordWithIDWasDeletedBlock is called.
It is important to note I'm using this exact same code to monitor shared records of another type and it works. It's actually a record that is a child of a child in this record. So I'm not sure why this top level/parent record would not show as being deleted when queried. The record does delete from CloudKit when Device A deletes it (both on its private DB and on the shared DB of DeviceB). So it's not a relationship or restriction of any kind on CloudKit I don't believe.
Welcome any thoughts how to chase this one down. Been at this for a week.
This is the code I'm using to subscribe...
+ (void) subscribeToGameChangesInDB:(int)dbID success:(void (^) (void))success failure:(void (^)(NSError *error))failure
{
CKNotificationInfo *notif = [[CKNotificationInfo alloc] init];
notif.alertLocalizationKey = @"DB Changed";
notif.shouldBadge = NO;
notif.shouldSendContentAvailable = YES;
CKDatabase *db = [CloudKitManager getDBForIdentifier:dbID];
if (dbID == kCKDBPublic)
{
NSLog(@"ERROR : Cannot subscribe to database changes in the PUBLIC DB.");
}
else
{
CKDatabaseSubscription *subscription = [[CKDatabaseSubscription alloc] initWithSubscriptionID:[NSString stringWithFormat:@"AllDBhanges-%@", [CloudKitManager getDatabaseNameForScope:db.databaseScope]]];
subscription.notificationInfo = notif;
[CloudKitManager saveSubscription:subscription inDB:db success:success failure:failure];
}
}
+ (void) saveSubscription:(CKSubscription*)subscription inDB:(CKDatabase*)db success:(void (^) (void))success failure:(void (^)(NSError *error))failure
{
[db saveSubscription:subscription completionHandler:^(CKSubscription * _Nullable subscription, NSError * _Nullable error)
{
if (error)
{
if ((error.code == CKErrorServerRejectedRequest) || (error.code == CKErrorUnknownItem)) // We probably already subscribed from another device.
{
NSLog(@"Already Subscribed... Passing Success. (code:%ld)", (long)error.code);
success ();
}
else
{
failure (error);
}
}
else
{
success();
}
}];
}
Here is the code I'm using to handle the notification and do the two operations above.
- (void) handleDatabaseNotification:(CKDatabaseNotification*)dbNotif
{
CKDatabase *db = [self getDatabaseForNotificationDatabaseScope:dbNotif.databaseScope];
[self handleDatabase:db changesWithPreviousChangeToken:[self getServerDBChangeTokenFromDisk] withCompletion:^(NSError * _Nonnull error) {
if (error)
{
NSLog (@"ERROR : HAndling Database change notificiation : %@", error);
}
}];
}
- (void) handleDatabase:(CKDatabase*)db changesWithPreviousChangeToken:(CKServerChangeToken*)previousChangeToken withCompletion:(void (^)(NSError *error))completion
{
CKFetchDatabaseChangesOperation *operation = [[CKFetchDatabaseChangesOperation alloc] initWithPreviousServerChangeToken:previousChangeToken];
[operation setRecordZoneWithIDChangedBlock:^(CKRecordZoneID * _Nonnull zoneID) {
NSLog(@"ZONE CHANGE : %@", zoneID);
[self handleDatabase:db zoneID:zoneID changesWithPreviousChangeToken:[self getServerZonesChangeTokenFromDisk]];
}];
[operation setFetchDatabaseChangesCompletionBlock:^(CKServerChangeToken * _Nullable serverChangeToken, BOOL moreComing, NSError * _Nullable operationError) {
if (operationError)
{
NSLog (@"ERROR : Unable to fetch Zone changes : %@", operationError);
completion(operationError);
}
NSLog(@"New serverChangeToken : %@", serverChangeToken);
[self storeServerDBChangeTokenToDisk:serverChangeToken];
if (moreComing)
{
[self handleDatabase:db changesWithPreviousChangeToken:serverChangeToken withCompletion:completion];
}
else
completion (nil);
}];
[db addOperation:operation];
}
- (void) handleDatabase:(CKDatabase*)db zoneID:(CKRecordZoneID*)zoneID changesWithPreviousChangeToken:(CKServerChangeToken*)previousChangeToken
{
NSLog(@"Zone Changes. DB : %@\nzoneID : %@", db, zoneID);
CKFetchRecordZoneChangesConfiguration *config = [[CKFetchRecordZoneChangesConfiguration alloc] init];
config.previousServerChangeToken = previousChangeToken;
CKFetchRecordZoneChangesOperation *zoneOperation = [[CKFetchRecordZoneChangesOperation alloc] initWithRecordZoneIDs:@[zoneID] configurationsByRecordZoneID:@{zoneID: config}];
[zoneOperation setRecordZoneFetchCompletionBlock:^(CKRecordZoneID * _Nonnull recordZoneID, CKServerChangeToken * _Nullable serverChangeToken, NSData * _Nullable clientChangeTokenData, BOOL moreComing, NSError * _Nullable recordZoneError) {
if (recordZoneError)
{
NSLog(@"ERROR : recordZoneError : %@", recordZoneError);
}
else
{
[self storeServerZonesChangeTokenToDisk:serverChangeToken];
if (moreComing)
{
NSLog(@"*********** There's MORE COMING ***************");
[self handleDatabase:db zoneID:zoneID changesWithPreviousChangeToken:serverChangeToken];
}
/* Handle changes */
}
}];
[zoneOperation setRecordChangedBlock:^(CKRecord * _Nonnull record) {
[self handleDatabaseRecordChange:record];
}];
[zoneOperation setFetchRecordZoneChangesCompletionBlock:^(NSError * _Nullable operationError) {
NSLog (@"setFetchRecordZoneChangesCompletionBlock");
if (operationError)
NSLog (@"setFetchRecordZoneChangesCompletionBlock ERROR : %@", operationError);
}];
[zoneOperation setRecordZoneChangeTokensUpdatedBlock:^(CKRecordZoneID * _Nonnull recordZoneID, CKServerChangeToken * _Nullable serverChangeToken, NSData * _Nullable clientChangeTokenData) {
NSLog(@"setRecordZoneChangeTokensUpdatedBlock Called : \n\nserverChangeToken = %@\n\nclientChangeTokenData = %@\n", serverChangeToken, clientChangeTokenData);
}];
[zoneOperation setRecordWithIDWasDeletedBlock:^(CKRecordID * _Nonnull recordID, CKRecordType _Nonnull recordType) {
NSManagedObjectContext *context = [self.coreManager getContext];
[context performBlockAndWait:^{
[self handleDeleteRecordID:recordID recordType:recordType];
[self.coreManager deleteContext:context];
}];
}];
[db addOperation:zoneOperation];
}
It's been a while since I ran into this issue, so the details are fuzzy. But I opened a support ticket with an Apple engineer about this.
The net of it (this was like 2018) was that when a parent record of a
CKShare
is deleted, and that record was shared with you, you will only get a database change notification. Your access to the record has ended, so there is no record-level info about it.I asked the engineer if there was a way to know if the record was deleted or unshared or why it changed, and he said there wasn't a way to know.
So I think the way to handle this is to just infer that the record is not accessible and then re-query for it. You'll then know if it's available or not.