2016-09-27 20 views
0

私のコードは、バックグラウンドでhandleBackgroundTasks:を介してWKWatchConnectivityRefreshBackgroundTaskを受け入れるためにフォアグラウンドでWCSessionDelegateコールバックのみを使用するように修正しようとしています。ドキュメンテーションは、バックグラウンドタスクが非同期的に来る可能性があり、WCSessionhasContentPendingNOになるまでsetTaskCompletedを呼び出すべきではないことを述べています。WKessionDelegateと競合するWKWatchConnectivityRefreshBackgroundTask

時計アプリをバックグラウンドに入れて、iPhoneアプリからtransferUserInfo:を受け取った場合、私は最初にWKWatchConnectivityRefreshBackgroundTaskを受け取ることができます。しかし、hasContentPendingは常にYESであるので、私は仕事を省き、私のWCSessionDelegateメソッドから単に戻ってきます。再度transferUserInfo:になると、hasContentPendingNOになりますが、このメッセージにはWKWatchConnectivityRefreshBackgroundTaskは関連付けられていません。つまり、その後transferUserInfo:handleBackgroundTask:への呼び出しをトリガーしません。これらは単にWCSessionDelegateによって処理されます。ただちにsetTaskCompletedをチェックしなくてもhasContentPendingの場合、transferUserInfo:session:didReceiveUserInfo:によって処理され、WCSessionを再びアクティブにする必要はありません。

ここで何をすべきかわかりません。 WCSessionを強制的に非アクティブ化する方法はないようで、遅れに関するドキュメントに従うと、setTaskCompletedはOSに問題を引き起こしているようです。

GitHubに私のワークフローを示すサンプルプロジェクトを投稿して文書化し、私のWKExtensionDelegateコードを貼り付けました。間違った選択をしたり、文書のどこかに間違って解釈していますか?

QuickSwitch 2.0ソースコード(Swift 3のバグを修正した後、rdar:// 28503030)を見てきましたが、それらのメソッドは動作していないようです(これについてはanother SO thread)。私はWCSessionhasContentPendingactivationStateにKVOを使用しようとしましたが、まだ完了している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拡張子に通信するシステム/バックグラウンドで再開、
  • ウォッチキット拡張機能は、実行したい作業が完了した時点をシステムに知らせるため、終了/再開することができます。

これは、いつでも、着信WatchConnectivityペイロードがウォッチに受信され、あなたのWatchKit拡張子があなたがバックグラウンドで実行されている理由を知らせる1つのhandleBackgroundTasks:コールバックを期待するべきである終了または中断されることを意味します。つまり、1つのWKWatchConnectivityRefreshBackgroundTaskを受け取ることができますが、いくつかのWatchConnectivityコールバック(ファイル、userInfos、applicationContext)を受け取ることができます。 hasContentPendingは、WCSessionが最初のすべての保留中のコンテンツ(ファイル、userInfos、applicationContext)を配信したことを通知します。その時点で、WKWatchConnectivityRefreshBackgroundTaskオブジェクトのsetTaskCompletedを呼び出す必要があります。

他のhandleBackgroundTasks:コールバックを受信して​​いないため、WKバックグラウンドタスクオブジェクトが完了していない限り、WatchKit拡張機能がすぐに停止または終了することが予想されます。

私は、OSが通常のようにそれらを一時停止しない可能性があるデバッガでプロセスに接続すると、そのような種類のものを避けるために、ロギングを使用して動作を調べることを推奨します問題の

+0

面白い、ありがとう。私は確かにデバッガに接続されたシミュレータでのみテストされているので、私はこれを信じることができます。これが真であれば、KVOは正しい動きのように聞こえる( 'BackgroundTask'sを取り除き、' hasContentPending'オブザーバが 'NO'にトリガされたときにクリアします)。私が答えとしてマークに戻る前にもう少しテストをしましょう。 – greg

+1

さて、シミュレータは、デバッガが接続されているデバイスよりも予測される動作の精度が低いことがわかりました。シミュレータでは、プロセスが実行されると再び停止することはないので、記述した内容と一致することがわかりました。 – ccjensen

関連する問題