如何订阅 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 分钟后都会启动拉取。
在 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 分钟后都会启动拉取。