Я пытаюсь адаптировать свой код с использованием только WCSessionDelegate
обратных вызовов на переднем плане принятия WKWatchConnectivityRefreshBackgroundTask
через handleBackgroundTasks:
в фоновом режиме. В документации указано, что фоновые задачи могут выполняться асинхронно и что не следует звонить setTaskCompleted
до тех пор, пока WCSession
не будет hasContentPending
равен NO
.WKWatchConnectivityRefreshBackgroundTask конкурируя с WCSessionDelegate
Если я поместил приложение для часов в фоновом режиме и transferUserInfo:
из приложения для iPhone, я смог успешно получить свой первый WKWatchConnectivityRefreshBackgroundTask
. Однако hasContentPending
всегда YES
, поэтому я сохраняю задачу и просто возвращаюсь из своего метода WCSessionDelegate
. Если I 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 об этом). Я пробовал использовать 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