如何订阅 CloudKit 中 public 数据库的更改?

How to subscribe to changes for a public database in CloudKit?

在 CloudKit 中订阅 public 数据库的最佳方式是什么? 我有一个 table 的人。每个人都包含一个名字和一个位置。 一旦位置发生变化,位置就会在 CloudKit 中更新。 那部分工作正常。

但是我无法在有记录更新时收到通知。

有些例子会很有帮助,因为我已经研究了可能的选项。 我查看了在数据库中保存订阅的选项以及 CKModifySubscriptionsOperation 选项。

目前,我的订阅代码如下所示:

let predicate = NSPredicate(format: "TRUEPREDICATE")
let newSubscription = CKQuerySubscription(recordType: "Person", predicate: predicate, options: [.firesOnRecordCreation, .firesOnRecordDeletion, .firesOnRecordUpdate])
let info = CKSubscription.NotificationInfo()
info.shouldSendContentAvailable = true
newSubscription.notificationInfo = info

database.save(newSubscription, completionHandler: {
      (subscription, error) in
      if error != nil {
          print("Error Creating Subscription")
          print(error)
      } else {
          userSettings.set(true, forKey: "subscriptionSaved")
      }
})

有人可以告诉我我的 AppDelegate 应该是什么样子吗?

我已将 didReceiveRemoteNotification 函数添加到我的 AppDelegate。我还调用了 application.registerForRemoteNotifications()。这就是我的 didReceiveRemoteNotification 函数的样子: 印刷品甚至都没有来找我。

func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable : Any], fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) {
    print("Notification!!")

    let notification = CKNotification(fromRemoteNotificationDictionary: userInfo) as? CKDatabaseNotification
    if notification != nil {
        AppData.checkUpdates(finishClosure: {(result) in
            OperationQueue.main.addOperation {
                completionHandler(result)
            }
        })
    }
}

我正在使用 RxCloudKit 库,这是它如何处理查询通知的代码片段 -

public func applicationDidReceiveRemoteNotification(userInfo: [AnyHashable: Any], fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) {
  let dict = userInfo as! [String: NSObject]
  let notification = CKNotification(fromRemoteNotificationDictionary: dict)
        
  switch notification.notificationType {
  case CKNotificationType.query:
    let queryNotification = notification as! CKQueryNotification
    self.delegate.query(notification: queryNotification, fetchCompletionHandler: completionHandler)
...

此方法是从 func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable: Any], fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void)

调用的

在收到通知之前,您需要执行以下操作 -

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
    // Override point for customization after application launch.
    application.registerForRemoteNotifications()        
...

更新: Info.plist 应包含以下内容 -

<key>UIBackgroundModes</key>
<array>
  <string>fetch</string>
  <string>remote-notification</string>
</array>

以下是您可以检查的其他一些内容:

= 1 =

确保代码中定义的 CloudKit 容器与您在 CloudKit 仪表板中访问的容器相同。有时我们在创建和测试多个容器时会忽略我们在 Xcode 中选择的内容作为 CloudKit 容器。

= 2 =

检查 CloudKit 仪表板中的 订阅 选项卡,并确保在启动应用程序时创建了 Person 订阅。如果您看到它,请尝试在 CK 仪表板中将其删除,然后再次 运行 您的应用并确保它再次显示。

= 3 =

检查 CK 仪表板中的日志。每当发送推送通知时,它们都会显示 push 类型的日志条目。如果当您 update/add 在 CK 仪表板中记录时它正在记录它,那么您就知道问题出在您的设备上。

= 4 =

请记住,推送通知在 iOS 模拟器中不起作用。您需要一个实际的设备(如果您正在制作 macOS 应用程序,则需要 Mac)。

= 5 =

通过广泛的测试,我发现如果您始终设置 alertBody,即使它是空白的,通知也会更可靠。像这样:

let info = CKSubscription.NotificationInfo()
info.shouldSendContentAvailable = true
info.alertBody = "" //This needs to be set or pushes don't always get sent
subscription.notificationInfo = info

= 6 =

对于 iOS 应用程序,我的应用程序委托处理这样的通知:

class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterDelegate {

  func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {

    //Ask Permission for Notifications
    UNUserNotificationCenter.current().delegate = self
    UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .badge, .sound], completionHandler: { authorized, error in
      DispatchQueue.main.async {
        if authorized {
          UIApplication.shared.registerForRemoteNotifications()
        }
      }
    })

    return true
  }

  //MARK: Background & Push Notifications
  func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable : Any]{
    let dict = userInfo as! [String: NSObject]
    let notification = CKNotification(fromRemoteNotificationDictionary: dict)

    if let sub = notification.subscriptionID{
      print("iOS Notification: \(sub)")
    }
  }

  //After we get permission, register the user push notifications
  func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
    //Add your CloudKit subscriptions here...
  }

}

如果您只是进行后台推送,则不需要获得通知权限,但对于用户以弹出通知形式看到的任何内容,您必须获得权限。如果您的应用未请求该权限,请尝试将其从您的设备中删除并在 Xcode.

中重新构建

祝你好运! :)

Update:正如 Reinhard 在他的评论中提到的那样:您实际上仍然可以从 public 数据库订阅更改并手动将更改导入 Core Data。如果 Apple 特别提到这些差异,我仍然不确定依赖 public 数据库中的订阅是否是个好主意

原回答:

我认为接受的答案没有完全回答这里的问题。 简短的回答是 CloudKit public 数据库不支持像私有数据库这样的订阅。相反,只能使用轮询机制。 NSPersistentCloudKitContainer 自动处理此问题,但很少更新。

WWDC2020 的这个演讲对此进行了详细解释,我建议您观看它,因为在 public 数据库与私有数据库不同的地方还提到了其他重要细节:https://developer.apple.com/wwdc20/10650

在谈话中,他们提到每次启动应用程序时以及应用程序使用约 30 分钟后都会启动拉取。