2016-09-27 62 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