WKWatchConnectivityRefreshBackgroundTask 与 WCSessionDelegate 竞争
WKWatchConnectivityRefreshBackgroundTask competing with WCSessionDelegate
我正在尝试调整我的代码,从仅在前台使用 WCSessionDelegate
回调到在后台通过 handleBackgroundTasks:
接受 WKWatchConnectivityRefreshBackgroundTask
。文档指出后台任务可能会异步进入,并且在 WCSession
的 hasContentPending
为 NO
之前不应调用 setTaskCompleted
。
如果我将我的手表应用程序置于后台并从 iPhone 应用程序 transferUserInfo:
,我能够成功接收到我的第一个 WKWatchConnectivityRefreshBackgroundTask
。但是,hasContentPending
始终是 YES
,因此我省去了任务,只是从我的 WCSessionDelegate
方法中 return。如果我再次 transferUserInfo:
,hasContentPending
是 NO
,但是没有 WKWatchConnectivityRefreshBackgroundTask
与此消息关联。也就是说,后续 transferUserInfo:
不会触发对 handleBackgroundTask:
的调用——它们只是由 WCSessionDelegate
处理。即使我立即 setTaskCompleted
而没有检查 hasContentPending
,后续的 transferUserInfo:
也会由 session:didReceiveUserInfo:
处理,而无需我再次激活 WCSession
。
我不知道该做什么。似乎没有办法强制 WCSession
停用,并且遵循有关延迟 setTaskCompleted
的文档似乎让我遇到 OS.[=44= 的麻烦]
我已经发布并记录了一个示例项目,说明了我在 GitHub 上的工作流程,并在下面粘贴了我的 WKExtensionDelegate
代码。我是否做出了错误的选择或错误地解释了文档中的某处?
我已经查看了关于此的 QuickSwitch 2.0 source code (after fixing the Swift 3 bugs, rdar://28503030), and their method simply doesn't seem to work (there's )。我已经尝试将 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
根据你的描述和我的理解,这听起来好像工作正常。
向我解释的方式是 watchOS 上的新 handleBackgroundTasks:
旨在成为一种方式:
- 系统与 WatchKit 扩展通信,为什么它在后台 launched/resumed,并且
- WatchKit 扩展让系统知道它何时完成它想做的工作的一种方式,因此可以再次 terminated/suspended。
这意味着每当 Watch 收到传入的 WatchConnectivity 负载并且您的 WatchKit 扩展被终止或暂停时,您应该期待一个 handleBackgroundTasks:
回调让您知道为什么您 运行 在后台.这意味着您可以收到 1 WKWatchConnectivityRefreshBackgroundTask
但多个 WatchConnectivity 回调(文件、userInfos、applicationContext)。 hasContentPending
会在您的 WCSession
交付所有初始的待处理内容(文件、userInfos、applicationContext)时通知您。那时,您应该在 WKWatchConnectivityRefreshBackgroundTask
对象上调用 setTaskCompleted。
那么您可以预期您的 WatchKit 扩展将很快被挂起或终止,除非您收到其他 handleBackgroundTasks:
回调并因此有其他 WK 后台任务对象要完成。
我发现,当使用调试器附加到进程时,操作系统可能不会像往常一样暂停它们,因此如果您想确保避免任何这些,建议使用日志记录检查此处的行为问题类型。
我正在尝试调整我的代码,从仅在前台使用 WCSessionDelegate
回调到在后台通过 handleBackgroundTasks:
接受 WKWatchConnectivityRefreshBackgroundTask
。文档指出后台任务可能会异步进入,并且在 WCSession
的 hasContentPending
为 NO
之前不应调用 setTaskCompleted
。
如果我将我的手表应用程序置于后台并从 iPhone 应用程序 transferUserInfo:
,我能够成功接收到我的第一个 WKWatchConnectivityRefreshBackgroundTask
。但是,hasContentPending
始终是 YES
,因此我省去了任务,只是从我的 WCSessionDelegate
方法中 return。如果我再次 transferUserInfo:
,hasContentPending
是 NO
,但是没有 WKWatchConnectivityRefreshBackgroundTask
与此消息关联。也就是说,后续 transferUserInfo:
不会触发对 handleBackgroundTask:
的调用——它们只是由 WCSessionDelegate
处理。即使我立即 setTaskCompleted
而没有检查 hasContentPending
,后续的 transferUserInfo:
也会由 session:didReceiveUserInfo:
处理,而无需我再次激活 WCSession
。
我不知道该做什么。似乎没有办法强制 WCSession
停用,并且遵循有关延迟 setTaskCompleted
的文档似乎让我遇到 OS.[=44= 的麻烦]
我已经发布并记录了一个示例项目,说明了我在 GitHub 上的工作流程,并在下面粘贴了我的 WKExtensionDelegate
代码。我是否做出了错误的选择或错误地解释了文档中的某处?
我已经查看了关于此的 QuickSwitch 2.0 source code (after fixing the Swift 3 bugs, rdar://28503030), and their method simply doesn't seem to work (there's 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
根据你的描述和我的理解,这听起来好像工作正常。
向我解释的方式是 watchOS 上的新 handleBackgroundTasks:
旨在成为一种方式:
- 系统与 WatchKit 扩展通信,为什么它在后台 launched/resumed,并且
- WatchKit 扩展让系统知道它何时完成它想做的工作的一种方式,因此可以再次 terminated/suspended。
这意味着每当 Watch 收到传入的 WatchConnectivity 负载并且您的 WatchKit 扩展被终止或暂停时,您应该期待一个 handleBackgroundTasks:
回调让您知道为什么您 运行 在后台.这意味着您可以收到 1 WKWatchConnectivityRefreshBackgroundTask
但多个 WatchConnectivity 回调(文件、userInfos、applicationContext)。 hasContentPending
会在您的 WCSession
交付所有初始的待处理内容(文件、userInfos、applicationContext)时通知您。那时,您应该在 WKWatchConnectivityRefreshBackgroundTask
对象上调用 setTaskCompleted。
那么您可以预期您的 WatchKit 扩展将很快被挂起或终止,除非您收到其他 handleBackgroundTasks:
回调并因此有其他 WK 后台任务对象要完成。
我发现,当使用调试器附加到进程时,操作系统可能不会像往常一样暂停它们,因此如果您想确保避免任何这些,建议使用日志记录检查此处的行为问题类型。