Уведомлять приложение WatchKit об обновлении без запроса приложения часов

Мне известно о возможностях методов WKInterfaceController openParentApplication и handleWatchKitExtensionRequest для приложения часов, позволяющего открывать родительское приложение и отправлять/получать данные.

Но как насчет этого... В случае, когда пользователь использует родительское приложение и выполняет действие в родительском приложении (т.е. меняет цвет фона), как я могу немедленно уведомить приложение часов и выполнить соответствующее действие на часы тоже?

Я считаю, что MMWormhole будет достаточно в этом примере, это лучший подход, который я должен использовать, или есть альтернатива ?


person Andrew Davis    schedule 02.03.2015    source источник


Ответы (4)


Задний план

Прежде всего, давайте подытожим то, что мы знаем. У нас есть

  • приложение, работающее на iPhone (далее я буду называть его приложением для iPhone)
  • app that runs on Watch...specifically
    • UI that runs on Watch
    • код, который работает на iPhone как расширение.

Для нас наиболее важны первая и последняя строки. Да, расширение поставляется в AppStore вместе с вашим приложением для iPhone, однако эти две вещи могут работать отдельно в операционной системе iOS. Следовательно, расширение и приложение для iPhone — это два разных процесса — две разные программы, работающие в ОС.

Из-за этого мы не можем использовать [NSNotificationCenter defaultCenter], потому что при попытке NSLog() defaultCenter на iPhone и defaultCenter в расширении они будут иметь разные адреса памяти.

Дарвин в помощь!

Как вы можете себе представить, такая проблема не нова для разработчиков, ее правильный термин — межпроцессное взаимодействие. Итак, в OS X и iOS есть... механизм уведомлений Дарвина. И самый простой способ его использования — реализовать несколько методов из класса CFNotificationCenter.

Пример

При использовании CFNotificationCenter вы увидите, что он очень похож на NSNotificationCenter. Я предполагаю, что NSNotif.. был построен вокруг CFNotif.. но я не подтвердил эту гипотезу. Теперь к делу.

Итак, давайте предположим, что вы хотите отправить уведомление с iPhone на Watch туда и обратно. Первое, что мы должны сделать, это зарегистрироваться на уведомления.

- (void)registerToNotification
{    
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didReceivedNSNotification) name:@"com.example.MyAwesomeApp" object:nil];

    CFNotificationCenterAddObserver(CFNotificationCenterGetDarwinNotifyCenter(), (__bridge const void *)(self), didReceivedDarwinNotification, CFSTR("NOTIFICATION_TO_WATCH"), NULL, CFNotificationSuspensionBehaviorDrop);
}

Вам, наверное, интересно, почему я добавил наблюдателя для NSNotificationCenter? Чтобы выполнить нашу задачу, нам нужно создать некоторый цикл, вы увидите это через мгновение.

Что касается второго способа.

CFNotificationCenterGetDarwinNotifyCenter() — получить Центр уведомлений Дарвина

(__bridge const void *)(self) - наблюдатель уведомлений

didReceivedDarwinNotification - метод callBack, срабатывает, когда объект получает уведомление. В основном это то же самое, что и @selector в NSNotification.

CFSTR("NOTIFICATION_TO_WATCH") - название уведомления, та же история в NSNotification, но здесь нам нужен метод CFSTR для преобразования строки в CFStringRef

И, наконец, последние два параметра object и suspensionBehaviour — оба игнорируются, когда мы используем DarwinNotifyCenter.

Круто, вот мы и зарегистрировались как наблюдатели. Итак, давайте реализуем наши методы обратного вызова (их два, один для CFNotificationCenter и один для NSNotificationCenter).

void didReceivedDarwinNotification()
{
    [[NSNotificationCenter defaultCenter] postNotificationName:@"com.example.MyAwesomeApp" object:nil];
}

Теперь, как видите, этот метод не начинается с - (void)Name.... Почему? Потому что это метод C. Вы понимаете, зачем нам здесь нужен NSNotificationCenter? Из метода C у нас нет доступа к self. Один из вариантов — объявить статический указатель на себя, например так: static id staticSelf назначьте его staticSelf = self, а затем используйте из didReceivedDarwinNotification: ((YourClass*)staticSelf)->_yourProperty, но я думаю, что NSNotificationCenter — лучший подход.

Итак, в селекторе, который отвечает на ваше NSNotification:

- (void)didReceivedNSNotification
{
    // you can do what you want, Obj-C method
}

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

Для этого нам нужна всего одна строка кода.

CFNotificationCenterPostNotification(CFNotificationCenterGetDarwinNotifyCenter(), CFSTR("NOTIFICATION_TO_WATCH"), (__bridge const void *)(self), nil, TRUE);

который может быть в вашем ViewController или Model.

Опять же, мы хотим получить CFNotificationCenterGetDarwinNotifyCenter(), затем указываем имя для уведомления, объект, который публикует уведомление, объект словаря (игнорируется при использовании DarwinNotifyCenter, и последние параметры являются ответом на вопрос: доставить немедленно?

Аналогичным образом вы можете отправить уведомление с часов на iPhone. По понятным причинам я предлагаю использовать другое имя уведомления, например CFSTR("NOTIFICATION_TO_IPHONE"), чтобы избежать ситуации, когда, например, iPhone отправляет уведомление на часы и себе.

Подводить итоги

MMWormhole — прекрасный и хорошо написанный класс, даже с тестами, которые охватывают большую часть, если не весь код. Его легко использовать, просто не забудьте предварительно настроить группы приложений. Однако, если вы не хотите импортировать сторонний код в свой проект или не хотите использовать его по какой-либо другой причине, вы можете использовать реализацию, представленную в этом ответе. Особенно, если вы не хотите/нужно обмениваться данными между iPhone и Watch.

Есть и второй хороший проект LLBSDMessaging. Он основан на сокетах Беркли. Более сложный и основанный на более низкоуровневом коде. Вот ссылка на длинный, но хорошо написанный пост в блоге, там вы найдете ссылку на Github. http://ddeville.me/2015/02/interprocess-communication-on-ios-with-berkeley-sockets/.

Надеюсь, это поможет.

person lvp    schedule 02.03.2015
comment
Это, пожалуй, самый полезный ответ, который у меня когда-либо был на вопрос о SO. Спасибо! - person Andrew Davis; 03.03.2015
comment
Это отличная лекция! Возможно, мой шаблон проектирования, полученный из этой концепции, полезен для кого-то еще: я использовал его для создания категории NSObject DarwinNotification в Obj-C, где я мог бы встроить код в C. Файл Bridging-Header.h дает моим классам Swift доступ к удобные методы из этой категории: registerToDarwinNotification, unregisterFromDarwinNotification, postDarwinNotification и didReceiveNSNotification. Мое доказательство концепции было успешным с первой попытки без каких-либо проблем. Спасибо! - person Computerspezl; 12.03.2015
comment
Можно ли получить этот ответ и в Swift? Та же проблема, что и у ОП. Спасибо! - person GarySabo; 11.08.2015
comment
Из какого метода следует вызывать registerToNotification? - person SAHM; 17.04.2017

Я полагаю, что вы, возможно, решили свою проблему сейчас. Но с «watchOS 2» есть лучший способ без использования сторонних классов. Вы можете использовать sendMessage:replyHandler:errorHandler: метод WCSession класса Watch Connectivity. Это будет работать, даже если ваше приложение для iOS не запущено.

А для получения дополнительной информации вы можете обратиться к этому блог.

person arjavlad    schedule 13.10.2015

Ответ Ivp выше хороший. Однако я хотел бы добавить, что использование уведомлений может быть сложным, и я хотел бы поделиться своим опытом.

Во-первых, я добавил наблюдателей в метод «awakeWithContext». Проблема: Уведомления давались несколько раз. Итак, я добавил «removeObserver:self» перед добавлением наблюдателя. Проблема: наблюдатель не будет удален, когда «я» отличается. (См. также здесь.)

В итоге я поместил следующий код в «willActivate»:

// make sure the the observer is not added several times if this function gets called more than one time
[[NSNotificationCenter defaultCenter] removeObserver:self name:@"com.toWatch.todo.updated" object:nil];
CFNotificationCenterRemoveObserver( CFNotificationCenterGetDarwinNotifyCenter(), (__bridge const void *)( self ), CFSTR( "NOTIFICATION_TO_WATCH_TODO_UPDATED" ), NULL );

[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector( didReceivedNSNotificationTodo ) name:@"com.toWatch.todo.updated" object:nil];
CFNotificationCenterAddObserver( CFNotificationCenterGetDarwinNotifyCenter(), (__bridge const void *)( self ), didReceivedDarwinNotificationTodo, CFSTR( "NOTIFICATION_TO_WATCH_TODO_UPDATED" ), NULL, CFNotificationSuspensionBehaviorDrop );

Я также добавил следующее в «didDeactivate»:

[[NSNotificationCenter defaultCenter] removeObserver:self name:@"com.toWatch.todo.updated" object:nil];
CFNotificationCenterRemoveObserver( CFNotificationCenterGetDarwinNotifyCenter(), (__bridge const void *)( self ), CFSTR( "NOTIFICATION_TO_WATCH_TODO_UPDATED" ), NULL );

Если уведомление отправляется в приложение Watch, когда оно неактивно, оно не доставляется.

Итак, в дополнение к описанному выше механизму уведомлений, который может информировать активное приложение Watch об изменении, сделанном на iPhone, я использую NSUserDefaults и общую группу приложений (подробнее), чтобы сохранить информацию. Когда контроллер на часах становится активным, он проверяет NSUserDefaults и при необходимости обновляет представление.

person vomako    schedule 04.05.2015

В WatchOS 2 вы можете sendMessage использовать такой метод;

Родительское приложение

импортировать WatchConnectivity затем;

Добавьте это в метод didFinishLaunchingWithOptions в AppDelegate;

if #available(iOS 9.0, *) {
    if WCSession.isSupported() {
        let session = WCSession.defaultSession()
        session.delegate = self
        session.activateSession()

        if !session.paired {
            print("Apple Watch is not paired")
        }
        if !session.watchAppInstalled {
            print("WatchKit app is not installed")
        }
    } else {
        print("WatchConnectivity is not supported on this device")
    }
} else {
     // Fallback on earlier versions
}

Затем в вашей функции уведомления;

func colorChange(notification: NSNotification) {
     if #available(iOS 9.0, *) {
        if WCSession.defaultSession().reachable {
           let requestValues = ["color" : UIColor.redColor()]
           let session = WCSession.defaultSession()

           session.sendMessage(requestValues, replyHandler: { _ in
                    }, errorHandler: { error in
                        print("Error with sending message: \(error)")
                })
            } else {
                print("WCSession is not reachable to send data Watch App from iOS")
            }
     } else {
         print("Not available for iOS 9.0")
     }
 }

Приложение для просмотра

Не забудьте импортировать WatchConnectivity и добавить WCSessionDelegate в свой InterfaceController

override func awakeWithContext(context: AnyObject?) {
    super.awakeWithContext(context)

    // Create a session, set delegate and activate it
    if (WCSession.isSupported()) {
        let session = WCSession.defaultSession()
        session.delegate = self
        session.activateSession()
    } else {
        print("Watch is not supported!")
    }
}

func session(session: WCSession, didReceiveMessage message: [String : AnyObject], replyHandler: ([String : AnyObject]) -> Void) { 
    if let deviceColor = message["color"] as? UIColor {
        // do whatever you want with color
    }
}

Для этого ваше приложение для часов должно работать на переднем плане.

person Kemal Can Kaynak    schedule 07.01.2016