我试图将我的代码从前台回调中仅使用WCSessionDelegate
回调改为通过handleBackgroundTasks:
在后台接受WKWatchConnectivityRefreshBackgroundTask
。该文档指出,后台任务可能异步进入,并且不应该调用setTaskCompleted
,直到WCSession
的hasContentPending
为NO
。WKWatchConnectivityRefreshBackgroundTask与WCSessionDelegate竞争
如果我从iPhone应用程序将我的手表应用程序放入背景中并且transferUserInfo:
能够成功接收我的第一个WKWatchConnectivityRefreshBackgroundTask
。但是,hasContentPending
始终为YES
,因此我省去了该任务,并简单地从我的WCSessionDelegate
方法中返回。如果我再次使用transferUserInfo:
,则hasContentPending
为NO
,但不存在与此消息关联的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的WCSession
的hasContentPending
和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
有趣,谢谢。我承认只是在连接到调试器的模拟器上进行测试,所以我可以相信这一点。如果这是真的,那么KVO听起来像是正确的动作(保存任何'BackgroundTask's,并且当'hasContentPending'观察者被触发为'NO'时清除它们)。在我回过头来回答之前,让我再做一些测试。 – greg
是的,我发现仿真器对于预期行为的准确度要比附加调试器的设备要准确得多。我在模拟器中发现,一旦进程正在运行,它不会再次挂起,因此与您所描述的内容相符。 – ccjensen