swift Swift - CloudKit订阅 - 3

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了swift Swift - CloudKit订阅 - 3相关的知识,希望对你有一定的参考价值。

See: https://www.invasivecode.com/weblog/advanced-cloudkit-part-iii

01. Each time a change occurs, a remote push notification will be sent to the device. This notification includes the recordID of the changed record together with the database (public or private) and the reason that the push notification was trigged. There are three possible reasons that could have triggered the notification: a record has changed, a new record has been added, or an existing record has been deleted. When you create a subscription you have to specify which record type you want to track and the reason (creation, deletion or change). This is an example of how you would create a subscription in Swift:

func subscribeToWordDefinitionChanges() {
    let predicate = NSPredicate(format: "TRUEPREDICATE")
    let subscription = CKSubscription(recordType: "WordDefinitions", predicate: predicate, options: [.FiresOnRecordCreation, .FiresOnRecordUpdate, .FiresOnRecordDeletion])
    let publicDatabase = CKContainer.defaultContainer().publicCloudDatabase
 
    publicDatabase.saveSubscription(subscription) { (subscription: CKSubscription?, error: NSError?) -> Void in
        guard error == nil else {
            // Handle the error here
            return
        }
 
        // Save that we have subscribed successfully to keep track and avoid trying to subscribe again
    }
}

02. Once your App successfully saves a subscription, there is no need to subscribe again. In fact, if you try to save the same subscription again, you will receive an error from CloudKit. From now on, you only have to process the push notifications when they arrive and take the pertinent action based on the reason of the change.

You need to register to push notifications in the application:didFinishLaunchingWithOptions: method of your App’s delegate.

func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
 
    // Push notification setup
    let notificationSettings = UIUserNotificationSettings(forTypes: UIUserNotificationType.Alert, categories: nil)
    application.registerUserNotificationSettings(notificationSettings)
    application.registerForRemoteNotifications()
 
    // Your code here
 
    return true
}

03. Then, implement the following method of the AppDelegate to handle the push notifications:

func application(application: UIApplication, didReceiveRemoteNotification userInfo: [NSObject : AnyObject]) {
    let cloudKitNotification = CKNotification(fromRemoteNotificationDictionary: userInfo as! [String : NSObject])
    if cloudKitNotification.notificationType == .Query {
        let queryNotification = cloudKitNotification as! CKQueryNotification
        if queryNotification.queryNotificationReason == .RecordDeleted {
            // If the record has been deleted in CloudKit then delete the local copy here
        } else {
            // If the record has been created or changed, we fetch the data from CloudKit
            let database: CKDatabase
            if queryNotification.isPublicDatabase {
                database = CKContainer.defaultContainer().publicCloudDatabase
            } else {
                database = CKContainer.defaultContainer().privateCloudDatabase
            }
            database.fetchRecordWithID(queryNotification.recordID!, completionHandler: { (record: CKRecord?, error: NSError?) -> Void in
                guard error == nil else {
                    // Handle the error here
                    return
                }
 
                if queryNotification.queryNotificationReason == .RecordUpdated {
                    // Use the information in the record object to modify your local data
                } else {
                    // Use the information in the record object to create a new local object
                }
            })
        }
    }
}

04. The above code is quite self explanatory. First we check if we are receiving a CKQueryNotification. If so, we identify the reason that triggered the notification. If a record has been deleted, we delete our local copy of the data. If the other two cases, first, we fetch the updated data from the server, and then we modify or create a new object, depending on the reason.

As you can see, everything till now seems quite simple. But at this point, you should already be asking yourself, what will happen if the App does not receive the push notification? And indeed, there is no guarantee that the push notification will be delivered. The device could be in airplane mode when lots of records get changed on the server. What is going to happen when the device is online again? Will it receive all the notifications triggered or only the last one, because push notifications are coalesced?

The answer is that the device will only receive the last notification. So how can the App be informed of all the changes that had occurred while it was unavailable? Fortunately, CloudKit covers this scenario with the CKFetchNotificationChangesOperation.

When your App launches, or becomes active, or it receives a push notification, it should first check with CloudKit if there are CKNotification objects pending to be processed. These are notifications that were not delivered to the App, because of different reasons, and we want to retrieve and process them.

Create a CKFetchNotificationChangesOperation operation and assign it a block to its notificationChangedBlock property. This block will be executed once for each notification. In this block, perform the operations with your data depending on the reason that triggered the notification (create, update or delete).

For each notification processed, save its notificationId property in an array. Then, in the fetchNotificationChangesCompletionBlock property, assign a block that will be executed at the end of the operation, when all the notifications are processed. If no error is fired, we will create another type of operation. This time we will use a CKMarkNotificationsReadOperation and we will pass in its initialization the array of notification ids that we have processed. This allows us to mark these notifications as read in the server, so the next time we ask for the notifications, they will not be served again.

func fetchNotificationChanges() {
    let operation = CKFetchNotificationChangesOperation(previousServerChangeToken: nil)
 
    var notificationIDsToMarkRead = [CKNotificationID]()
 
    operation.notificationChangedBlock = { (notification: CKNotification) -> Void in
        // Process each notification received
        if notification.notificationType == .Query {
            let queryNotification = notification as! CKQueryNotification
            let reason = queryNotification.queryNotificationReason
            let recordID = queryNotification.recordID
 
            // Do your process here depending on the reason of the change
 
            // Add the notification id to the array of processed notifications to mark them as read
            notificationIDsToMarkRead.append(queryNotification.notificationID!)
        }
    }
 
    operation.fetchNotificationChangesCompletionBlock = { (serverChangeToken: CKServerChangeToken?, operationError: NSError?) -> Void in
        guard operationError == nil else {
            // Handle the error here
            return
        }
 
        // Mark the notifications as read to avoid processing them again
        let markOperation = CKMarkNotificationsReadOperation(notificationIDsToMarkRead: notificationIDsToMarkRead)
        markOperation.markNotificationsReadCompletionBlock = { (notificationIDsMarkedRead: [CKNotificationID]?, operationError: NSError?) -> Void in
            guard operationError == nil else {
                // Handle the error here
                return
            }
        }
 
        let operationQueue = NSOperationQueue()
        operationQueue.addOperation(markOperation)
    }
 
    let operationQueue = NSOperationQueue()
    operationQueue.addOperation(operation)
}

Take note that when the fetchNotificationChangesCompletionBlock, there is the possibility that not all of the notifications pending to be processed have been served in this operation. This is due to the limits in size and records of the amount of data that a CloudKit petition can serve. If this is the case, the operation moreComing property will be true. You should then start a new CKFetchNotificationChangesOperation passing to it the CKServerChangeToken received in the block.

以上是关于swift Swift - CloudKit订阅 - 3的主要内容,如果未能解决你的问题,请参考以下文章

swift Swift - CloudKit - 获取用户ID

swift Swift - CloudKit - 推送通知

swift Swift - CloudKit共享

swift Swift - CloudKit - 维护本地缓存

swift Swift - CloudKit - 部署架构

swift Swift - CloudKit订阅 - 5