我在 Apple Watch 上有一个自定义复杂功能,我试图每小时更新一次。它应该每小时 ping 一个 API 端点,如果数据自上次检查以来发生了变化,则应更新复杂性。
这是我目前所拥有的,似乎只有一次的效果。当它起作用时,它确实会 ping 我的服务器并更新复杂性。看来 WatchOS 并没有每小时调用一次我的计划任务。我缺少更好的标准做法吗?
@implementation ExtensionDelegate
- (void)applicationDidFinishLaunching {
// Perform any final initialization of your application.
[SessionManager sharedManager];
[self scheduleHourlyUpdate];
}
- (void) scheduleHourlyUpdate {
NSDate *nextHour = [[NSDate date] dateByAddingTimeInterval:(60 * 60)];
NSDateComponents *dateComponents = [[NSCalendar currentCalendar]
components: NSCalendarUnitYear | NSCalendarUnitMonth | NSCalendarUnitDay | NSCalendarUnitHour | NSCalendarUnitMinute | NSCalendarUnitSecond fromDate:nextHour];
[[WKExtension sharedExtension] scheduleBackgroundRefreshWithPreferredDate:nextHour userInfo:nil scheduledCompletion:^(NSError * _Nullable error) {
// schedule another one in the next hour
if (error != nil)
NSLog(@"Error while scheduling background refresh task: %@", error.localizedDescription);
}];
}
- (void)handleBackgroundTasks:(NSSet<WKRefreshBackgroundTask *> *)backgroundTasks {
// Sent when the system needs to launch the application in the background to process tasks. Tasks arrive in a set, so loop through and process each one.
for (WKRefreshBackgroundTask * task in backgroundTasks) {
// Check the Class of each task to decide how to process it
if ([task isKindOfClass:[WKApplicationRefreshBackgroundTask class]]) {
// Be sure to complete the background task once you’re done.
WKApplicationRefreshBackgroundTask *backgroundTask = (WKApplicationRefreshBackgroundTask*)task;
[backgroundTask setTaskCompletedWithSnapshot:NO];
[self updateComplicationServer];
} else if ([task isKindOfClass:[WKSnapshotRefreshBackgroundTask class]]) {
// Snapshot tasks have a unique completion call, make sure to set your expiration date
WKSnapshotRefreshBackgroundTask *snapshotTask = (WKSnapshotRefreshBackgroundTask*)task;
[snapshotTask setTaskCompletedWithDefaultStateRestored:YES estimatedSnapshotExpiration:[NSDate distantFuture] userInfo:nil];
} else if ([task isKindOfClass:[WKWatchConnectivityRefreshBackgroundTask class]]) {
// Be sure to complete the background task once you’re done.
WKWatchConnectivityRefreshBackgroundTask *backgroundTask = (WKWatchConnectivityRefreshBackgroundTask*)task;
[backgroundTask setTaskCompletedWithSnapshot:NO];
} else if ([task isKindOfClass:[WKURLSessionRefreshBackgroundTask class]]) {
// Be sure to complete the background task once you’re done.
WKURLSessionRefreshBackgroundTask *backgroundTask = (WKURLSessionRefreshBackgroundTask*)task;
[backgroundTask setTaskCompletedWithSnapshot:NO];
} else if ([task isKindOfClass:[WKRelevantShortcutRefreshBackgroundTask class]]) {
// Be sure to complete the relevant-shortcut task once you’re done.
WKRelevantShortcutRefreshBackgroundTask *relevantShortcutTask = (WKRelevantShortcutRefreshBackgroundTask*)task;
[relevantShortcutTask setTaskCompletedWithSnapshot:NO];
} else if ([task isKindOfClass:[WKIntentDidRunRefreshBackgroundTask class]]) {
// Be sure to complete the intent-did-run task once you’re done.
WKIntentDidRunRefreshBackgroundTask *intentDidRunTask = (WKIntentDidRunRefreshBackgroundTask*)task;
[intentDidRunTask setTaskCompletedWithSnapshot:NO];
} else {
// make sure to complete unhandled task types
[task setTaskCompletedWithSnapshot:NO];
}
}
}
- (void)updateComplicationServer {
[self scheduleHourlyUpdate];
NSString *nsLogin = [NSUserDefaults.standardUserDefaults objectForKey:@"loginDTO"];
if (nsLogin != nil)
{
NSDateComponents *dateComponents = [[NSCalendar currentCalendar]
components: NSCalendarUnitYear | NSCalendarUnitMonth | NSCalendarUnitDay fromDate:[NSDate date]];
LoginDTO *login = new LoginDTO([nsLogin cStringUsingEncoding:NSUTF8StringEncoding]);
NSMutableURLRequest *req = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:@"https://www.myurl.com/Api/Watch/Complication"]];
[req setHTTPMethod:@"GET"];
// Set headers
[req addValue:[NSString stringWithUTF8String:login->GetApiKey()] forHTTPHeaderField:@"MySessionKey"];
[req addValue:[NSString stringWithFormat:@"%d,%d,%d", dateComponents.year, dateComponents.month, dateComponents.day] forHTTPHeaderField:@"FetchDate"];
[req addValue:@"application/json" forHTTPHeaderField:@"Content-Type"];
NSURLSession *session = [NSURLSession sharedSession];
NSURLSessionDataTask *task = [session dataTaskWithRequest:req completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error)
{
// Call is complete and data has been received
NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse*)response;
if (httpResponse.statusCode == 200)
{
NSString* nsJson = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
NSString *prevJson = [NSUserDefaults.standardUserDefaults objectForKey:@"previousComplicationJson"];
if (prevComplicationJson != nil)
{
if ([prevComplicationJson isEqualToString:nsJson])
return; // Nothing changed, so don't update the UI.
}
// Update the dictionary
[NSUserDefaults.standardUserDefaults setObject:nsJson forKey:@"previousComplicationJson"];
CLKComplicationServer *server = [CLKComplicationServer sharedInstance];
for (int i = 0; i < server.activeComplications.count; i++)
[server reloadTimelineForComplication:server.activeComplications[i]];
}
}];
[task resume];
delete login;
}
}
watchOS 后台任务的实现和调试非常麻烦,但根据 Apple 的文档和其他人讨论过的实现,我认为这是最佳实践。我在这里看到几个问题。
首先,从WKRefreshBackgroundTask 文档 https://developer.apple.com/documentation/watchkit/wkrefreshbackgroundtask/2868454-settaskcompletedwithsnapshot:
所有后台任务完成后,系统会立即暂停扩展。
Calling setTaskCompletedWithSnapshot
任务上的 向系统表明您已经完成了需要执行的所有工作,因此它将挂起您的应用程序。你的updateComplicationServer
方法可能永远没有机会运行,因为系统过早挂起您的扩展。
更重要的是,要在后台更新期间发出 URL 请求,您需要使用后台 URL 会话。这WKRefreshBackgroundTask 文档中概述的示例流程 https://developer.apple.com/documentation/watchkit/wkapplicationrefreshbackgroundtask显示了进行此设置的最佳实践。简而言之:
- 您可以使用以下命令安排后台刷新
WKExtension
’s scheduleBackgroundRefresh
就像你正在做的那样。
- 系统将在您首选日期之后的某个时间(由系统自行决定)唤醒您的分机
WKRefreshBackgroundTask
.
- 在您的扩展委托中
handle
方法,检查WKApplicationRefreshBackgroundTask
;而不是使用URLSessionDataTask
在这里,您需要安排一个背景URL 会话,以便系统可以挂起您的分机并代表您执行请求。请参阅WKURLSessionRefreshBackgroundTask https://developer.apple.com/documentation/watchkit/wkurlsessionrefreshbackgroundtask有关如何设置后台会话的详细信息,请参阅文档。
-
系统将在单独的进程中执行您的 URL 请求,并在完成后再次唤醒您的扩展程序。它将调用您的扩展代表handle
方法和以前一样,这次使用WKURLSessionRefreshBackgroundTask
。在这里,您需要做两件事:
- 将后台任务保存在扩展委托的实例变量中。我们还不想将其设置为完整,但我们需要保留它以便稍后在 URL 请求完成时设置完成。
- 使用后台任务创建另一个后台 URLsession
sessionIdentifier
, and 使用您的扩展委托作为会话的委托(为什么使用另一个对象作为委托不起作用,我不能说,但这似乎是一个至关重要的细节)。请注意,使用相同的标识符创建第二个 URL 会话允许系统将会话连接到它在另一个进程中为您执行的下载;第二个后台 URL 会话的目的只是将委托与会话连接起来。
-
在会话委托中,实现urlSession(_ downloadTask: didFinishDownloadingTo:)
and urlSession(task: didCompleteWithError:)
功能。
与基于块的不同NSURLSessionDataTask
,后台 URL 请求始终作为下载任务执行。系统执行请求并为您提供包含结果数据的临时文件。在里面urlSession(_ downloadTask: didFinishDownloadingTo:)
函数、该文件中的数据并根据需要对其进行处理以更新您的 UI。
最后,在代表处urlSession(task: didCompleteWithError:)
函数、调用setTaskCompletedWithSnapshot
告诉系统您已经完成了工作。唷。
正如我所提到的,这对于调试来说真的很令人沮丧,主要是因为当这些事情实际发生时(如果它们真的发生),这完全取决于系统。苹果的文档对于分配给后台刷新的预算有这样的说法:
一般来说,系统每小时为扩展坞中的每个应用程序(包括最近使用的应用程序)执行大约一项任务。该预算由扩展坞上的所有应用程序共享。系统每小时为每个应用程序执行多项任务,并在活动表盘上执行复杂操作。该预算由表盘上的所有复杂功能共享。用尽预算后,系统会延迟您的请求,直到有更多可用时间。
最后一点:传说 watchOS 模拟器无法正确处理后台 URL 刷新任务,但不幸的是,Apple 的文档对此没有官方说明。如果可以的话,最好在 Apple Watch 硬件上进行测试。
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)