CloudKit — 为 CloudKit 配置消息通知

CloudKit 配置消息通知主要用于当 CloudKit 中的数据发生更新时,客户端可以实时接收到通知。这对于需要在多个设备之间同步数据、协作功能或需要对云端数据更改做出反馈的场景非常有用。

CloudKit 中,消息通知由 订阅 (Notifications/Subcriptions) 功能实现。开发者可以通过创建 CKSubscription 来指定需要监听的事件或数据变动,并触发通知,从而在客户端响应。


CloudKit 消息通知的主要类型 #

CloudKit 支持以下两种类型的通知:

  1. 本地推送通知(Silent Notification)
  • 仅在 App 运行时通过 CKFetchNotificationChangesOperation 来获取变更。
  • 对用户透明,用户不会收到通知提示。
  1. 远程推送通知(Push Notifications)
  • iCloud 服务发送一条推送消息到设备。
  • 用户会收到通知提示(或后台响应通知)。
  • 使用这种方法需要启用 Apple Push Notification 服务 (APNs)。

配置 CloudKit 消息通知的步骤 #

1. 启用 Push Notifications (远程通知) #

在 Xcode 中,需要先为应用开启推送通知支持才能收到远程消息通知:

  1. 配置 Capabilities
  • 打开 Xcode 项目,点击目标(Target)。

  • 导航到 Signing & Capabilities

  • 添加 Push NotificationsBackground Modes

  • 勾选 Background FetchRemote Notifications

  1. 申请 APNs 证书(默认已自动配置)
  • 在 Apple Developer 网站检查你的 App 是否启用了 Push Notifications

2. 定义服务器端事件订阅(CloudKit 中的 CKSubscription) #

订阅(CKSubscription) 是 CloudKit 的机制,用于在数据库、记录、或区域上创建事件监听器。当满足特定条件时,会生成一个通知。

  • 创建一个 CKSubscription

  • 订阅特定 Record Type 的特定数据变动,用于监听服务器上的数据更改。

  • 感知:新增、删除或修改记录。

示例代码:订阅创建 #

以下是如何为一个名为 NoteRecord Type 配置订阅:

import CloudKit

func setupCloudKitSubscription() {
    let recordType = "Note" // 监听的 Record 类型
    let subscriptionID = "NoteEventSubscription" // 订阅唯一标识符
    
    // 创建订阅条件 (监听新增或变更事件)
    let subscription = CKQuerySubscription(
        recordType: recordType,
        predicate: NSPredicate(value: true), // 监听所有 Note 类型记录
        subscriptionID: subscriptionID,
        options: [.firesOnRecordCreation, .firesOnRecordUpdate, .firesOnRecordDeletion]
    )
    
    // 配置通知请求内容
    let notificationInfo = CKSubscription.NotificationInfo()
    notificationInfo.title = "CloudKit Updated"
    notificationInfo.alertBody = "A Note record has been updated in CloudKit."
    notificationInfo.shouldSendContentAvailable = true // 支持后台静默通知
    notificationInfo.soundName = "default" // 启用通知声音
    
    subscription.notificationInfo = notificationInfo
    
    // 保存到 CloudKit 数据库
    CKContainer.default().privateCloudDatabase.save(subscription) { (savedSubscription, error) in
        if let error = error {
            print("Failed to save subscription: \(error.localizedDescription)")
        } else {
            print("Subscription saved successfully: \(savedSubscription)")
        }
    }
}

3. 响应客户端的远程通知 #

在客户端收到 CloudKit 推送通知后,可以通过多种方式处理消息更新。以下是处理远程推送的流程:

注册远程通知 #

AppDelegateSceneDelegate 中,注册推送通知:

import UIKit

@main
class AppDelegate: UIResponder, UIApplicationDelegate {
    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        // 请求推送通知授权
        UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .badge, .sound]) { granted, error in
            if granted {
                DispatchQueue.main.async {
                    application.registerForRemoteNotifications()
                }
                print("Notification permission granted!")
            } else {
                print("Notification permission denied!")
            }
        }
        return true
    }
}

在用户同意后,iOS 将向设备注册远程通知,并允许你的 App 接收推送。

处理远程通知 #

处理通知时,可以解析 CloudKit 的通知负载,并获取事件详情。

  1. 实现 didReceiveRemoteNotification 来处理通知:
import CloudKit

func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable: Any],
                 fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) {
    // 解析 CloudKit 推送
    let notification = CKNotification(fromRemoteNotificationDictionary: userInfo)
    if let queryNotification = notification as? CKQueryNotification {
        print("Received notification for RecordType: \(String(describing: queryNotification.recordType))")
        
        if let recordID = queryNotification.recordID {
            fetchUpdatedRecord(with: recordID)
        }
    }
    completionHandler(.newData)
}

// 拉取更新后的数据
func fetchUpdatedRecord(with recordID: CKRecord.ID) {
    let database = CKContainer.default().privateCloudDatabase
    database.fetch(withRecordID: recordID) { record, error in
        if let record = record {
            print("Fetched updated record: \(record)")
        } else if let error = error {
            print("Error fetching updated record: \(error.localizedDescription)")
        }
    }
}
  1. CKNotification 类型
    推送中的通知类型可能是以下之一:
  • CKQueryNotification:表示一个记录的变更。
  • CKDatabaseNotification:表示数据库范围内的更改。

通过解析 CKNotification 可以获取更新的记录 ID 或其他相关信息。


4. 验证订阅是否成功 #

您可以通过 CloudKit Dashboard 或直接检查 CloudKit 模式的 Subscriptions 确认订阅是否创建成功。

或者通过代码列出当前的订阅:

func fetchSubscriptions() {
    CKContainer.default().privateCloudDatabase.fetchAllSubscriptions { subscriptions, error in
        if let error = error {
            print("Failed to fetch subscriptions: \(error.localizedDescription)")
        } else if let subscriptions = subscriptions {
            print("Existing subscriptions: \(subscriptions)")
        }
    }
}

5. 处理后台消息更新 #

确保您的 App 可以在后台接收和处理消息通知。需要在 Capabilities 中启用 Background Modes,启用以下功能:

  • Remote Notifications

后台时,推送的 contentAvailable: true 将触发 didReceiveRemoteNotification


常见问题与注意事项 #

  1. 订阅只能在真实设备上测试
  • 推送通知和远程订阅需要真实设备,无法在模拟器上测试。
  1. 订阅的持久化
  • 订阅是存储在 CloudKit 服务器端的,每个用户为独立的配置,如果需要动态更新订阅,需记录订阅的 ID 并删除后重新创建。
  1. 限制与性能
  • 对于每个容器,存在订阅数限制(每个容器最多允许 100 条订阅)。
  • 如果订阅数量过多可能影响性能。
  1. 防止重复订阅
  • 在运行时检查是否已存在订阅,避免重复创建。

参考文档 #

本文共 1664 字,上次修改于 Jan 5, 2025