Подтвердить что ты не робот

NSUserDefaultsDidChangeNotification и Today Extensions

Я разрабатываю приложение для iPhone с расширением Today. Приложение имеет модуль Model, который загружает из/сохраняет в NSUserDefaults. Поскольку я хочу, чтобы эта информация была доступна как для основного приложения, так и для расширения, я использую группу приложений:

let storage = NSUserDefaults(suiteName: "group.etc.etc.etc...")

Как приложение, так и расширение могут получить доступ к информации без каких-либо проблем.

Основное приложение иногда может создавать локальное уведомление для представления пользователю. Это уведомление имеет два действия, связанных с ним (UIUserNotificationAction). Одно из этих действий вызывает запуск некоторого кода на фоне основного приложения. Этот код изменяет информацию NSUserDefaults и запускает синхронизацию. Мой код выглядит примерно так:

func application(application: UIApplication, handleActionWithIdentifier id: String?, forLocalNotification not: UILocalNotification, completionHandler: () -> ()) {
    // Interact with model here
    // New information gets saved to NSUserDefaults
    userDefaultsStorage.synchronize()
    completionHandler()
}

Теперь, на Today Ext. Я, естественно, замечаю любые изменения, внесенные в информацию о NSUserDefaults, чтобы я мог перезагрузить интерфейс виджета:

override func viewDidLoad() {
    super.viewDidLoad()

    // ...

    NSNotificationCenter.defaultCenter().addObserverForName(NSUserDefaultsDidChangeNotification, object: nil, queue: NSOperationQueue.mainQueue()) { _ in
        self.reload()
    }
}

Теперь вот моя проблема:

  • Основное приложение планирует UILocalNotification. Я открываю сегодняшнее представление и смотрю на мой сегодняшний виджет.

  • При срабатывании уведомления в верхней части экрана появляется баннер.

  • Я спускаюсь на этот баннер, чтобы показать два действия, и я выбираю тот, который я упоминал ранее (сегодняшний виджет по-прежнему жив и на экране).

Я знаю, что действие выполняется правильно в фоновом режиме и что изменения вносятся в информацию о NSUserDefaults.

Однако, несмотря на то, что сегодняшний виджет был активным и на экране все это время, действие перезагрузки не запускается. После дальнейшего исследования я могу подтвердить, что NSUserDefaultsDidChangeNotification не работает не (я поставил точку останова, и она не запускалась, а также некоторые другие проверки).

Я знаю, что изменения происходят в результате действия уведомления, потому что если я заставляю перезагрузку виджета (закрывая и открывая представление сегодня), виджет обновляется правильно.

Я видел различные онлайн-учебники, где первое, что они говорят, это прослушивание этого уведомления и обновление виджета, чтобы "виджет синхронизировался с NSUserDefaults". Но дело в том, что AFAICT это уведомление абсолютно бесполезно! Как получилось?


Примечание 1: Когда я изменяю информацию об NSUserDefaults из текущего виджета, уведомление срабатывает правильно.

Примечание 2: Отладка сегодняшнего виджета абсолютно ужасна, кстати. Всегда нужно указывать Xcode "Прикрепить к процессу по имени...", прежде чем он сможет реагировать на точки останова и сбои. И iOS постоянно создает новый процесс для виджета, поэтому мне нужно постоянно указывать Xcode для присоединения снова.

4b9b3361

Ответ 1

Из doc здесь:

Cocoa включает два типа центров уведомлений: Класс NSNotificationCenter управляет уведомлениями в одном обработать. Класс NSDistributedNotificationCenter управляет уведомления по нескольким процессам на одном компьютере.

По-видимому, существующее приложение и сегодняшнее расширение - это разные процессы, так как при отладке сегодняшнего расширения вы хотите присоединить процесс приложения, но NSNotificationCenter работает только в рамках одного процесса.

Для связи между содержащимся приложением и расширениями вы можете использовать Центр уведомлений Darwin CFNotificationCenter, который работает как NSDistributedNotificationCenter, который доступен только для osx.

Идея заключается в использовании файла в папке группы, которую они разделяют. в содержащем приложении вы записываете данные, которые хотите отправить в файл, а затем отправляйте CFNotification, которые будут получены сегодня.

В сегодняшнем расширении используйте CFNotificationCenterAddObserver для просмотра CFNotification, после его получения вызывается обратный вызов, в котором NSNotification должна быть вывешена из-за обратного вызова, является функция стиля C, а "userInfo" не может быть передана в CFNotification, после получения этого объекта NSNotification, он начинает считывать данные из файла, который используется для обновления существующего внутреннего вида в Центре уведомлений.

Вы можете использовать этот код github для реализации принудительной загрузки сегодняшнего расширения. Это работает для меня.
Вот отличный пост по этому поводу. http://www.atomicbird.com/blog/sharing-with-app-extensions

Другой вариант - использовать функцию setHasContent. Когда вы планируете локальный идентификатор, установите для содержимого значение false, чтобы скрыть представление, в handleActionWithIdentifier установите для него значение true, чтобы отобразить представление. Таким образом, когда вы остаетесь в центре уведомлений, вы не увидите представление на мгновение, но когда вы его увидите, это будут обновленные данные.

let widgetController = NCWidgetController.widgetController()
widgetController.setHasContent(false, forWidgetWithBundleIdentifier: "YourTodayWidgetBundleIdentifier") 

Но я думаю, что вся проблема - редкий случай, который не нужно исправлять, поскольку вы можете обновить данные, перезагружая центр уведомлений или переключитесь на вкладку уведомлений и вернитесь на вкладку "Сегодня".