2016-09-27 60 views
0

我试图将我的代码从前台回调中仅使用WCSessionDelegate回调改为通过handleBackgroundTasks:在后台接受WKWatchConnectivityRefreshBackgroundTask。该文档指出,后台任务可能异步进入,并且不应该调用setTaskCompleted,直到WCSessionhasContentPendingNOWKWatchConnectivityRefreshBackgroundTask与WCSessionDelegate竞争

如果我从iPhone应用程序将我的手表应用程序放入背景中并且transferUserInfo:能够成功接收我的第一个WKWatchConnectivityRefreshBackgroundTask。但是,hasContentPending始终为YES,因此我省去了该任务,并简单地从我的WCSessionDelegate方法中返回。如果我再次使用transferUserInfo:,则hasContentPendingNO,但不存在与此消息关联的WKWatchConnectivityRefreshBackgroundTask。也就是说,随后的transferUserInfo:不会触发handleBackgroundTask:的调用 - 它们仅由WCSessionDelegate处理。即使我立刻setTaskCompleted没有检查hasContentPending,后续transferUserInfo:session:didReceiveUserInfo:处理,我不需要再次激活WCSession

我不确定这里要做什么。似乎没有办法强制WCSession停用,并且遵循关于延迟setTaskCompleted的文档似乎让我陷入了操作系统的麻烦。

我已经发布并记录了一个示例项目,说明了我的工作流程GitHub,粘贴了我的WKExtensionDelegate以下代码。我是否做出了错误的选择或者错误地解释文档中的某处?我已经看了QuickSwitch 2.0源代码(修复Swift 3 bug后,rdar:// 28503030),他们的方法似乎不工作(关于这个,有another SO thread)。我已经尝试使用KVO的WCSessionhasContentPendingactivationState,但仍然没有任何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

从您的描述和我的理解,它听起来像这是工作正常。

这是向我解释的方式是,在watchOS新handleBackgroundTasks:旨在一种方式:

  • 系统传达给WatchKit扩展为什么它正在启动/重新开始在后台,以及
  • WatchKit扩展的一种方式,让系统知道它何时完成了它想要完成的工作,因此可以再次终止/挂起。

这意味着,只要接收的观看传入WatchConnectivity有效载荷和你WatchKit扩展终止或暂停,你应该期待一个handleBackgroundTasks:回调,让你知道你为什么在后台运行。这意味着您可以接收1 WKWatchConnectivityRefreshBackgroundTask,但可以接收几个WatchConnectivity回调(文件,userInfos,applicationContext)。 hasContentPending可让您知道您的WCSession何时传送了所有初始未决内容(文件,userInfos,applicationContext)。此时,您应该调用WKWatchConnectivityRefreshBackgroundTask对象上的setTaskCompleted。

然后,您可以预计,您的WatchKit扩展将很快被暂停或终止,除非您已收到其他handleBackgroundTasks:回调并因此完成其他WK后台任务对象。

我发现,当用调试器附加到进程时,OS可能不会像通常那样暂停它们,所以它会建议使用日志记录检查这里的行为,如果你想确保避免任何类型的的问题。

+0

有趣,谢谢。我承认只是在连接到调试器的模拟器上进行测试,所以我可以相信这一点。如果这是真的,那么KVO听起来像是正确的动作(保存任何'BackgroundTask's,并且当'hasContentPending'观察者被触发为'NO'时清除它们)。在我回过头来回答之前,让我再做一些测试。 – greg

+1

是的,我发现仿真器对于预期行为的准确度要比附加调试器的设备要准确得多。我在模拟器中发现,一旦进程正在运行,它不会再次挂起,因此与您所描述的内容相符。 – ccjensen