Как подписаться на изменения для общедоступной базы данных в CloudKit?

Как лучше всего подписаться на общедоступную базу данных в CloudKit? У меня есть таблица с людьми. У каждого человека есть имя и местонахождение. После изменения местоположения оно обновляется в 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)
            }
        })
    }
}

person Peter van Leeuwen    schedule 06.11.2018    source источник
comment
Я не тот, кто просто переполняется стеком и случайным образом задает вопрос. Я провел тщательное исследование stackoverflow и других веб-сайтов, а также прочитал часть CloudKit в iBook о быстрой разработке IOS12. Пока безрезультатно.   -  person Peter van Leeuwen    schedule 07.11.2018
comment
Один вопрос. Можно ли вообще подписаться на уведомления публичной базы? ;) Это работает только для приватной и общей, но не публичной. Я прав?   -  person Bartłomiej Semańczyk    schedule 16.07.2019


Ответы (3)


Вот еще несколько вещей, которые вы можете проверить:

= 1 =

Убедитесь, что контейнер CloudKit, определенный в вашем коде, совпадает с контейнером, к которому вы обращаетесь на панели инструментов CloudKit. Иногда мы упускаем из виду то, что выбрали в Xcode в качестве контейнера CloudKit, когда создаем и тестируем несколько контейнеров.

= 2 =

Откройте вкладку Подписки на панели инструментов CloudKit и убедитесь, что ваша Person подписка создается при запуске приложения. Если вы видите его, попробуйте удалить его на панели инструментов CK, а затем снова запустите приложение и убедитесь, что оно снова отображается.

= 3 =

Проверьте журналы на панели управления CK. Они будут отображать запись в журнале типа push всякий раз, когда отправляется push-уведомление. Если это регистрируется при обновлении/добавлении записи на панели управления CK, значит, проблема связана с вашим устройством.

= 4 =

Помните, что push-уведомления не работают в симуляторе iOS. Вам нужно реальное устройство (или Mac, если вы делаете приложение для macOS).

= 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...
  }

}

Получение разрешения на уведомления не требуется, если вы выполняете только фоновые push-уведомления, но для всего, что пользователь видит в форме всплывающего уведомления, вы должны получить разрешение. Если ваше приложение не запрашивает это разрешение, попробуйте удалить его со своего устройства и снова создать в Xcode.

Удачи! :)

person Clifton Labrum    schedule 13.11.2018
comment
Спасибо!! Это помогло мне. Я использовал симулятор iPhone (пункт 4), после того, как я использовал свой настоящий iPhone, он начал получать уведомления. Большое спасибо! - person Peter van Leeuwen; 15.11.2018
comment
@PetervanLeeuwen: Привет, Питер. Я пытаюсь сделать почти то же самое, что вы описали в своем вопросе. Можете ли вы указать мне какие-либо книги, статьи или ссылки, которые помогли вам понять, как настроить уведомления CloudKit и подписаться на них? В настоящее время мне удалось успешно сохранять, редактировать и удалять данные в простом списке в общедоступном CloudKit на одном устройстве. Но любые изменения, которые я делаю на одном устройстве, не обновляются автоматически на другом устройстве, пока я не перезапущу приложение или не создам кнопку обновления вручную. Я ценю, если вы можете указать мне на ресурсы, которые помогли вам решить вашу проблему. Спасибо. - person Eric Wo; 15.10.2020

Я использую библиотеку 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>

person Maxim Volgin    schedule 07.11.2018
comment
Я обновил свой вопрос, чтобы вы могли видеть, каков мой текущий статус. Я уже пробовал вещи, которые вы предложили. - person Peter van Leeuwen; 07.11.2018
comment
А как насчет Info.plist? Включает ли он ‹key›UIBackgroundModes‹/key› ‹array› ‹string›fetch‹/string› ‹string›remote-notification‹/string› ‹/array› - person Maxim Volgin; 07.11.2018
comment
Он содержит эти значения - person Peter van Leeuwen; 08.11.2018
comment
Можно ли увидеть подписку запроса на панели управления CloudKit, если вы вошли в систему под той же учетной записью, которая использовалась для создания подписки? - person Maxim Volgin; 08.11.2018
comment
Да, подписка есть. - person Peter van Leeuwen; 09.11.2018
comment
Включены ли Push-уведомления в возможностях? - person Maxim Volgin; 09.11.2018
comment
Да, это также включается автоматически после включения cloudkit. - person Peter van Leeuwen; 12.11.2018
comment
Убедитесь, что ваше приложение получает push-уведомления в целом, например, через APNS. Проверьте, имеют ли переопределяемые вами методы AppDelegate одну и ту же сигнатуру — это часто является причиной того, что что-то не вызывается, как ожидалось. - person Maxim Volgin; 12.11.2018

Обновление. Как упомянул Райнхард в своем комментарии: вы все еще можете подписаться на изменения из общедоступной базы данных и вручную импортировать изменения в Core Data. Тем не менее я не уверен, стоит ли полагаться на подписки в общедоступной базе данных, если Apple специально упомянула об этих различиях.

Исходный ответ:

Я не думаю, что принятый ответ полностью отвечает на вопрос здесь. Краткий ответ будет заключаться в том, что общедоступная база данных CloudKit не поддерживает подписки, такие как частная база данных. Вместо этого можно использовать только механизм опроса. NSPersistentCloudKitContainer обрабатывает это автоматически, но обновляется очень редко.

введите здесь описание изображения

Это выступление на WWDC2020 подробно объясняет это, и я рекомендую посмотреть его, потому что упоминаются другие важные детали, в которых общедоступная база данных отличается от частной базы данных: https://developer.apple.com/wwdc20/10650

В разговоре они упомянули, что пул инициируется при каждом запуске приложения и примерно через 30 минут использования приложения.

person JonEasy    schedule 01.10.2020
comment
Спасибо за ваш комментарий спустя столько времени! :) - person Peter van Leeuwen; 01.10.2020
comment
Да, просто наткнулся на те же проблемы, что и у вас, и потратил часы на устранение неполадок здесь. Это, вероятно, больше не поможет вам, но, возможно, другим;) - person JonEasy; 01.10.2020
comment
К сожалению, вы не правы: общедоступная база данных CloudKit поддерживает подписку. В видео WWDC, которое вы привели, говорится, что локальная база данных CoreData обновляется путем опроса. Чего они не сказали, так это того, что вы можете дополнительно подписаться на изменения в общедоступной базе данных iCloud и будете получать push-уведомления после изменений. Затем вам нужно обновить CoreData с помощью переданной информации. Я делаю это в своем приложении, и оно работает без проблем. - person Reinhard Männer; 05.10.2020
comment
Очень интересно, спасибо, я не знал об этом. Хотя я не уверен, останется ли это поведение таким и продолжит ли оно работать в будущем, если они явно упомянут различия в своем выступлении. - person JonEasy; 05.10.2020
comment
На самом деле, они сказали, что CoreData+CloudKit с общедоступной базой данных использует не push-уведомления, а опрос для обновления CoreData при изменении iCloud. Возможно, причина в том, что для чтения общедоступных данных не обязательно авторизоваться в iCloud. Но если вы вошли в систему, вы можете написать запись о подписке и получать push-уведомления после изменений. - person Reinhard Männer; 06.10.2020