私のコードは、バックグラウンドでhandleBackgroundTasks:
を介してWKWatchConnectivityRefreshBackgroundTask
を受け入れるためにフォアグラウンドでWCSessionDelegate
コールバックのみを使用するように修正しようとしています。ドキュメンテーションは、バックグラウンドタスクが非同期的に来る可能性があり、WCSession
のhasContentPending
がNO
になるまでsetTaskCompleted
を呼び出すべきではないことを述べています。WKessionDelegateと競合するWKWatchConnectivityRefreshBackgroundTask
時計アプリをバックグラウンドに入れて、iPhoneアプリからtransferUserInfo:
を受け取った場合、私は最初にWKWatchConnectivityRefreshBackgroundTask
を受け取ることができます。しかし、hasContentPending
は常にYES
であるので、私は仕事を省き、私のWCSessionDelegate
メソッドから単に戻ってきます。再度transferUserInfo:
になると、hasContentPending
はNO
になりますが、このメッセージには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)。私はWCSession
のhasContentPending
とactivationState
に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
面白い、ありがとう。私は確かにデバッガに接続されたシミュレータでのみテストされているので、私はこれを信じることができます。これが真であれば、KVOは正しい動きのように聞こえる( 'BackgroundTask'sを取り除き、' hasContentPending'オブザーバが 'NO'にトリガされたときにクリアします)。私が答えとしてマークに戻る前にもう少しテストをしましょう。 – greg
さて、シミュレータは、デバッガが接続されているデバイスよりも予測される動作の精度が低いことがわかりました。シミュレータでは、プロセスが実行されると再び停止することはないので、記述した内容と一致することがわかりました。 – ccjensen