WKWatchConnectivityRefreshBackgroundTask 与 WCSessionDelegate 竞争
Posted
技术标签:
【中文标题】WKWatchConnectivityRefreshBackgroundTask 与 WCSessionDelegate 竞争【英文标题】:WKWatchConnectivityRefreshBackgroundTask competing with WCSessionDelegate 【发布时间】:2016-09-27 23:14:36 【问题描述】:我正在尝试调整我的代码,从仅在前台使用 WCSessionDelegate
回调到在后台通过 handleBackgroundTasks:
接受 WKWatchConnectivityRefreshBackgroundTask
。该文档指出后台任务可能会异步进入,并且在WCSession
的hasContentPending
为NO
之前不应调用setTaskCompleted
。
如果我将手表应用程序置于后台并从 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 错误之后,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
【问题讨论】:
【参考方案1】:根据您的描述和我的理解,这听起来像是正常工作。
向我解释的方式是,watchOS 上的新 handleBackgroundTasks:
旨在成为一种方式:
这意味着,每当 Watch 接收到传入的 WatchConnectivity 负载并且您的 WatchKit 扩展程序被终止或暂停时,您应该期待一个 handleBackgroundTasks:
回调,让您知道您为什么在后台运行。这意味着您可能会收到 1 个WKWatchConnectivityRefreshBackgroundTask
,但会收到几个 WatchConnectivity 回调(文件、userInfos、applicationContext)。 hasContentPending
让您知道您的 WCSession
何时交付了所有初始的、待处理的内容(文件、用户信息、应用程序上下文)。此时,您应该在 WKWatchConnectivityRefreshBackgroundTask
对象上调用 setTaskCompleted。
然后您可以预期您的 WatchKit 扩展将很快暂停或终止,除非您收到其他 handleBackgroundTasks:
回调并因此有其他 WK 后台任务对象要完成。
我发现,当使用调试器附加到进程时,操作系统可能不会像往常那样暂停它们,因此如果您想确保避免任何此类情况,建议使用日志记录检查此处的行为问题。
【讨论】:
很有趣,谢谢。诚然,我只在调试器附带的模拟器中进行了测试,所以我可以相信这一点。如果这是真的,那么 KVO 听起来是正确的举动(保存任何BackgroundTask
s,并在 hasContentPending
观察者被触发到 NO
时清除它们)。在我返回标记为已回答之前,让我再做一些测试。
是的,我发现模拟器对预期行为的准确度甚至低于连接调试器的设备。我在模拟器中发现,一旦进程运行,它就永远不会再次挂起,因此与您所描述的相符。以上是关于WKWatchConnectivityRefreshBackgroundTask 与 WCSessionDelegate 竞争的主要内容,如果未能解决你的问题,请参考以下文章