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

Уведомлять приложение WatchKit об обновлении без приложения просмотра, запрашивающего его

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

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

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

4b9b3361

Ответ 1

Фон

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

  • приложение, которое работает на iPhone (я буду называть его как приложение для iPhone)
  • приложение, которое работает на Watch... специально
    • Пользовательский интерфейс, который работает на Watch
    • который работает на iPhone как расширение.

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

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

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

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

Пример

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

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

- (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.... Зачем? Потому что это метод С. Вы видите, почему нам нужен 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 и последние параметры ответ на вопрос: доставить немедленно?

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

Подводя итог

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

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

Надеюсь на эту помощь.

Ответ 2

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

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

Ответ 3

Ответ на 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 о внесении изменений на iPhone, я использую NSUserDefaults и общую группу приложений (подробнее) для сохранения информации. Когда контроллер Watch Watch активируется, он проверяет NSUserDefaults и обновляет представление, если это необходимо.

Ответ 4

С помощью WatchOS 2 вы можете использовать метод sendMessage:

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

import 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
    }
}

Чтобы сделать это, приложение Watch должно работать на переднем плане.