Это чрезвычайно важно, так как Apple Watch обеспечивает второй экран / дополнительное периферийное оборудование для основного приложения, работающего на устройстве iOS — будь то пульт дистанционного управления или быстрый взгляд / взгляд на то, что происходит внутри более широкой картины.
В своем последнем посте я показал, как настроить удаленную регистрацию и инструментарий / аналитику в приложении Apple Watch с использованием сервера IBM MobileFirst Platform Foundation . Я использовал методы, описанные ниже, для связи между WatchKit и хост-приложениями в примере приложения из этого предыдущего поста.
Когда мы говорим о двунаправленной связи, мы говорим о передаче данных двумя способами:
- Отправка данных из хост-приложения в приложение WatchKit
- Отправка данных в приложение WatchKit из хост-приложения
Сначала вы можете подумать: «О, это просто, просто используйте NSNotificationCenter для связи между отдельными классами приложения», но все не так просто.
Приложение Apple Watch действительно состоит из 3 частей: 1) двоичного файла основного приложения iOS, 2) пользовательского интерфейса на Apple Watch и 3) двоичного файла расширения WatchKit (на устройстве iOS).
Apple Watch App — Архитектурные компоненты
Да, вы правильно прочитали, расширение WatchKit (которое контролирует всю логику в пользовательском интерфейсе Apple Watch и находится на устройстве iOS) представляет собой отдельный двоичный файл из «основного» двоичного файла приложения iOS. Это отдельные процессы, поэтому объекты в памяти в основном приложении не являются одними и теми же объектами в памяти в расширении, и в результате эти процессы не взаимодействуют напрямую. NSNotificationCenter не будет работать.
Однако, есть определенные способы заставить этот тип сценария работать.
Во-первых, в WatchKit есть методы для вызова действий в хост-приложении из расширения WatchKit. Методы openParentApplication или handleWatchKitExtensionRequest WatchKit предоставляют возможность вызывать действия и передавать данные в содержащем приложении, а также предоставляют механизм для вызова блока кода «ответ» обратно в расширении WatchKit после завершения выполнения кода в главном приложении.
Например, в расширении WatchKit это вызовет действие в хост-приложении и обработает ответ:
[WKInterfaceController openParentApplication:@{@"action":@"toggleStatus"} reply:^(NSDictionary *replyInfo, NSError *error) {
[logger trace:@"toggleStatus reply"];
[self updateUIFromHost:replyInfo];
}];
Внутри хост-приложения у нас есть доступ к userInfo NSDictionary, который был передан, и мы можем ответить на него соответствующим образом. Например, в приведенном ниже коде я устанавливаю строковое значение для экземпляра userInfo и предпринимаю соответствующие действия на основе значения этой строки.
- (void)application:(UIApplication *)application
handleWatchKitExtensionRequest:(NSDictionary *)userInfo
reply:(void (^)(NSDictionary *replyInfo))reply {
//handle this as a background task
__block UIBackgroundTaskIdentifier watchKitHandler;
watchKitHandler = [[UIApplication sharedApplication] beginBackgroundTaskWithName:@"backgroundTask"
expirationHandler:^{
watchKitHandler = UIBackgroundTaskInvalid;
}];
NSString *action = (NSString*) [userInfo valueForKey:@"action"];
[logger trace:@"incoming request from WatchKit: ", action];
LocationManager * locationManager = [LocationManager sharedInstance];
NSMutableDictionary *result = [[NSMutableDictionary alloc] init];
if ([action isEqualToString:@"toggleStatus"]) {
//toggle whether or not we're actually tracking the location
[locationManager toggleTracking];
} else if ([action isEqualToString:@"stopTracking"]) {
[locationManager stopTracking];
} else if ([action isEqualToString:@"currentStatus"]) {
//do nothing for now
}
NSString *trackingString = [NSString stringWithFormat:@"%s", locationManager.trackingActive ? "true" : "false"];
[result setValue:trackingString forKey:@"tracking"];
reply(result);
dispatch_after( dispatch_time( DISPATCH_TIME_NOW, (int64_t)NSEC_PER_SEC * 1 ), dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0 ), ^{
[[UIApplication sharedApplication] endBackgroundTask:watchKitHandler];
} );
}
Это относится к сценарию «вытягивания» и отлично подходит, если вы хотите вызывать действия в вашем хост-приложении из расширения WatchKit, а затем обрабатывать ответы обратно в расширении WatchKit, чтобы соответствующим образом обновить пользовательский интерфейс Apple Watch.
А как насчет сценария «толчка»? Предыдущий сценарий охватывает только запросы, которые исходят из расширения WatchKit. Что произойдет, если у вас внутри вашего хост-приложения запущен процесс, и у вас есть обновления, которые вы хотите отправить в расширение WatchKit без исходящего запроса?
Нет общей памяти, и это не общий процесс, поэтому не будет работать ни NSNotificationCenter, ни прямой вызов метода. Однако вы * можете * использовать уведомления Дарвина (которые работают в отдельных процессах с помощью CFNotificationCenter ). Это обеспечивает взаимодействие между процессами практически в реальном времени, и вы можете обмениваться данными в качестве атрибутов объекта CFdictionary, основанного на процессах. Вы также можете обмениваться большими объемами данных с помощью групп доступа и уведомлять отдельные процессы с помощью реализации CFNotificationCenter.
Примечание: CFNotificationCenter — это синтаксис языка C, а не синтаксис Objective-C.
Сначала вам нужно подписаться на уведомления в WatchKitExtension . Обратите внимание на экземпляр статического идентификатора «staticSelf»… это понадобится вам позже при вызове методов Objective-C из обратного вызова уведомлений C.
static id staticSelf;
- (void)awakeWithContext:(id)context {
[super awakeWithContext:context];
//add your initialization stuff here
CFNotificationCenterAddObserver(CFNotificationCenterGetDarwinNotifyCenter(), (__bridge const void *)(self), didReceiveTrackingStatusNotificaiton, CFSTR("TrackingStatusUpdate"), NULL, CFNotificationSuspensionBehaviorDrop);
staticSelf = self;
}
Из вашего хост-приложения вы можете вызвать CFNotificationCenterPostNotification для вызова дарвиновского уведомления.
-(void) postTrackingStatusNotificationToWatchKit {
NSString *trackingString = [NSString stringWithFormat:@"%s", self.trackingActive ? "true" : "false"];
NSDictionary *payload = @{@"tracking":trackingString};
CFDictionaryRef cfPayload = (__bridge CFDictionaryRef)payload;
CFNotificationCenterPostNotification(CFNotificationCenterGetDarwinNotifyCenter(), CFSTR("TrackingStatusUpdate"), (__bridge const void *)(self), cfPayload, TRUE);
}
Затем в расширении WatchKit обработайте уведомление и соответствующим образом обновите свое расширение WatchKit.
void didReceiveTrackingStatusNotificaiton() {
[staticSelf respondToPostFromHostApp];
}
Теперь мы рассмотрели сценарии, в которых вы можете запрашивать данные или действия в хост-приложении * из * расширения WatchKit, а также способы передачи данных из хост-приложения в расширение WatchKit.
Теперь, что, если бы была библиотека, которая инкапсулировала некоторые из них, и сделала ее еще проще для разработчика? Когда я писал приложение в своем предыдущем посте, я использовал методы, описанные выше. Однако недавно я наткнулся на MMWormhole с открытым исходным кодом , которая оборачивает метод Darwin Notifications (см. Выше) для простоты использования. Я уверен, что буду использовать это в своем следующем приложении WatchKit.