2016-09-27 3 views
0

Я пытаюсь адаптировать свой код с использованием только WCSessionDelegate обратных вызовов на переднем плане принятия WKWatchConnectivityRefreshBackgroundTask через handleBackgroundTasks: в фоновом режиме. В документации указано, что фоновые задачи могут выполняться асинхронно и что не следует звонить setTaskCompleted до тех пор, пока WCSession не будет hasContentPending равен NO.WKWatchConnectivityRefreshBackgroundTask конкурируя с WCSessionDelegate

Если я поместил приложение для часов в фоновом режиме и transferUserInfo: из приложения для iPhone, я смог успешно получить свой первый WKWatchConnectivityRefreshBackgroundTask. Однако hasContentPending всегда YES, поэтому я сохраняю задачу и просто возвращаюсь из своего метода WCSessionDelegate. Если I transferUserInfo: снова, hasContentPending - NO, но нет WKWatchConnectivityRefreshBackgroundTask, связанного с этим сообщением. То есть последующие transferUserInfo: не вызывают вызов handleBackgroundTask: - они просто обрабатываются WCSessionDelegate. Даже если я сразу же setTaskCompleted без проверки hasContentPending, последующие transferUserInfo: обрабатываются session:didReceiveUserInfo: без необходимости активации WCSession.

Я не уверен, что делать здесь. Кажется, что не существует способа отключить деактивацию WCSession, и, следуя документации по задержке setTaskCompleted, похоже, я столкнулся с проблемами с ОС.

Я опубликовал и задокументировал пример проекта, иллюстрирующий мой рабочий процесс на GitHub, вставив мой код WKExtensionDelegate ниже. Я делаю неправильный выбор или неправильно интерпретирую документацию где-то вдоль линии?

Я смотрел на QuickSwitch 2.0 исходного кода (после исправления ошибок Swift 3, rdar: // 28503030), и метод их просто не похоже на работу (там another SO thread об этом). Я пробовал использовать KVO для WCSessionhasContentPending и activationState, но до сих пор не существует WKWatchConnectivityRefreshBackgroundTask, что имеет смысл дать мое текущее объяснение проблемы.

#import "ExtensionDelegate.h" 

@interface ExtensionDelegate() 

@property (nonatomic, strong) WCSession *session; 
@property (nonatomic, strong) NSMutableArray<WKWatchConnectivityRefreshBackgroundTask *> *watchConnectivityTasks; 

@end 

@implementation ExtensionDelegate 

#pragma mark - Actions 

- (void)handleBackgroundTasks:(NSSet<WKRefreshBackgroundTask *> *)backgroundTasks 
{ 
    NSLog(@"Watch app woke up for background task"); 

    for (WKRefreshBackgroundTask *task in backgroundTasks) { 
     if ([task isKindOfClass:[WKWatchConnectivityRefreshBackgroundTask class]]) { 
      [self handleBackgroundWatchConnectivityTask:(WKWatchConnectivityRefreshBackgroundTask *)task]; 
     } else { 
      NSLog(@"Handling an unsupported type of background task"); 
      [task setTaskCompleted]; 
     } 
    } 
} 

- (void)handleBackgroundWatchConnectivityTask:(WKWatchConnectivityRefreshBackgroundTask *)task 
{ 
    NSLog(@"Handling WatchConnectivity background task"); 

    if (self.watchConnectivityTasks == nil) 
     self.watchConnectivityTasks = [NSMutableArray new]; 
    [self.watchConnectivityTasks addObject:task]; 

    if (self.session.activationState != WCSessionActivationStateActivated) 
     [self.session activateSession]; 
} 

#pragma mark - Properties 

- (WCSession *)session 
{ 
    NSAssert([WCSession isSupported], @"WatchConnectivity is not supported"); 

    if (_session != nil) 
     return (_session); 

    _session = [WCSession defaultSession]; 
    _session.delegate = self; 

    return (_session); 
} 

#pragma mark - WCSessionDelegate 

- (void)session:(WCSession *)session activationDidCompleteWithState:(WCSessionActivationState)activationState error:(NSError *)error 
{ 
    switch(activationState) { 
     case WCSessionActivationStateActivated: 
      NSLog(@"WatchConnectivity session activation changed to \"activated\""); 
      break; 
     case WCSessionActivationStateInactive: 
      NSLog(@"WatchConnectivity session activation changed to \"inactive\""); 
      break; 
     case WCSessionActivationStateNotActivated: 
      NSLog(@"WatchConnectivity session activation changed to \"NOT activated\""); 
      break; 
    } 
} 

- (void)sessionWatchStateDidChange:(WCSession *)session 
{ 
    switch(session.activationState) { 
     case WCSessionActivationStateActivated: 
      NSLog(@"WatchConnectivity session activation changed to \"activated\""); 
      break; 
     case WCSessionActivationStateInactive: 
      NSLog(@"WatchConnectivity session activation changed to \"inactive\""); 
      break; 
     case WCSessionActivationStateNotActivated: 
      NSLog(@"WatchConnectivity session activation changed to \"NOT activated\""); 
      break; 
    } 
} 

- (void)session:(WCSession *)session didReceiveUserInfo:(NSDictionary<NSString *, id> *)userInfo 
{ 
    /* 
    * NOTE: 
    * Even if this method only sets the task to be completed, the default 
    * WatchConnectivity session delegate still picks up the message 
    * without another call to handleBackgroundTasks: 
    */ 

    NSLog(@"Received message with counter value = %@", userInfo[@"counter"]); 

    if (session.hasContentPending) { 
     NSLog(@"Task not completed. More content pending..."); 
    } else { 
     NSLog(@"No pending content. Marking all tasks (%ld tasks) as complete.", (unsigned long)self.watchConnectivityTasks.count); 
     for (WKWatchConnectivityRefreshBackgroundTask *task in self.watchConnectivityTasks) 
      [task setTaskCompleted]; 
     [self.watchConnectivityTasks removeAllObjects]; 
    } 
} 

@end 

ответ

1

Из вашего описания и моего понимания звучит так, как будто это работает правильно.

Как это было объяснено мне, что новый handleBackgroundTasks: на watchOS призван быть способ:

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

Это означает, что всякий раз, когда входящий полезной нагрузки WatchConnectivity поступает на Дозоре и ваше расширение WatchKit будет прекращено или приостановлено, вы должны ожидать один handleBackgroundTasks: обратного вызова давая вам знать, почему вы работаете в фоновом режиме. Это означает, что вы можете получить 1 WKWatchConnectivityRefreshBackgroundTask, но несколько обратных вызовов WatchConnectivity (файлы, userInfos, applicationContext). hasContentPending позволяет узнать, когда ваш WCSession доставил все начальное, ожидающее содержимого (файлы, userInfos, applicationContext). В этот момент вы должны вызвать setTaskCompleted на объекте WKWatchConnectivityRefreshBackgroundTask.

Тогда вы можете ожидать, что расширение WatchKit вскоре будет приостановлено или прекращено до тех пор, пока вы не получите другие обратные вызовы и, следовательно, у вас есть другие объекты фоновой задачи WK.

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

+0

Интересно, спасибо. Я, по общему признанию, только тестировал в симуляторе, прикрепленном к отладчику, поэтому я могу поверить в это. Если это так, то KVO звучит как правильный ход (сэкономьте все «BackgroundTask's» и очистите их, когда «hasContentPending» наблюдатель запущен на «NO»). Позвольте мне сделать немного больше тестов, прежде чем я вернусь, чтобы отметить, как ответили. – greg

+1

Да, я обнаружил, что имитаторы были еще менее точными для ожидаемого поведения, чем на устройстве с прикрепленным отладчиком. Я нашел в симуляторах, что, как только процесс запускается, он никогда не приостанавливается, так что соответствует тому, что вы описываете. – ccjensen