安排并发症更新 [英] Scheduling complication updates

查看:72
本文介绍了安排并发症更新的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我在Apple Watch上有一个自定义并发症,我试图每小时更新一次.每小时应对API端点执行ping操作,如果上次检查后的数据已更改,则应更新并发症.

I have a custom complication on Apple Watch that I am trying to get to update once an hour. Each hour it should ping an API endpoint and if the data has changed from the last check, the complication should be updated.

这是我目前拥有的东西,似乎只能在一次蓝月亮中工作一次.当它起作用时,它确实可以ping通我的服务器并更新复杂性.看来WatchOS只是每小时不打电话给我安排的任务.我是否缺少更好的标准做法?

Here is what I currently have that only seems to work once in a blue moon. When it DOES work, it does indeed ping my server and update the complication. It appears WatchOS just isn't calling my scheduled tasked once per hour. Is there a better standard practice that I'm missing?

@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文档和实现,我认为这是最好的实践.我在这里看到了几个问题.

watchOS background tasks are extraordinarily cumbersome to implement and debug, but based on Apple’s documentation and implementations that others have discussed, here‘s what I believe to be the best practice. I see a couple of issues here.

首先,从 WKRefreshBackgroundTask文档:

所有后台任务完成后,系统会立即暂停扩展程序.

The system suspends the extension as soon as all background tasks are complete.

在任务上调用setTaskCompletedWithSnapshot表示系统已完成所需的所有工作,因此将挂起您的应用程序.您的updateComplicationServer方法可能永远都没有机会运行,因为系统过早挂起了扩展程序.

Calling setTaskCompletedWithSnapshot on the task indicates to the system that you’ve finished performing all the work you need to do, so it will suspend your app. Your updateComplicationServer method is probably never getting a chance to run because the system suspends your extension too early.

更重要的是,要在后台更新期间发出URL请求,您需要使用后台URL会话. WaRefreshBackgroundTask文档中概述的示例过程显示了进行此设置的最佳实践.简而言之:

More importantly, to make URL requests during a background update, you’ll need to use a background URL session. The example process outlined in the WKRefreshBackgroundTask docs shows the best practice for setting this up. In short:

  1. 您可以像执行操作一样使用WKExtensionscheduleBackgroundRefresh安排后台刷新.
  2. 系统会在您的首选日期(由系统决定)之后的某个时间用WKRefreshBackgroundTask唤醒您的扩展程序.
  3. 在您的扩展代表的handle方法中,检查WKApplicationRefreshBackgroundTask;而不是在此处使用URLSessionDataTask执行请求,您需要安排 background URL会话,以便系统可以暂停您的扩展并代表您执行请求.有关应如何设置后台会话的详细信息,请参见 WKURLSessionRefreshBackgroundTask 文档.
  4. 系统将在一个单独的过程中执行您的URL请求,并在扩展完成后再次唤醒您的扩展.它将像以前一样调用您的扩展代表的handle方法,这次使用WKURLSessionRefreshBackgroundTask.在这里,您需要做两件事:

  1. You schedule a background refresh using WKExtension’s scheduleBackgroundRefresh just like you’re doing.
  2. The system will wake your extension sometime after your preferred date (at the system’s discretion) with a WKRefreshBackgroundTask.
  3. In your extension delegate’s handle method, check for the WKApplicationRefreshBackgroundTask; instead of performing the request with a URLSessionDataTask here, you need to schedule a background URL session so that the system can suspend your extension and perform the request on your behalf. See the WKURLSessionRefreshBackgroundTask docs for details about how background sessions should be set up.
  4. The system will perform your URL request in a separate process, and again wake your extension once it has finished. It will call your extension delegate’s handle method like before, this time with a WKURLSessionRefreshBackgroundTask. Here, you need to do two things:

  • 将后台任务保存在扩展委托的实例变量中.我们暂时不希望将其设置为完整,但是我们需要保留它以便稍后在URL请求完成时将其设置为完整.
  • 使用后台任务的sessionIdentifier创建另一个后台URL会话,并使用扩展程序委托作为会话的委托(为什么我不能将另一个对象用作委托,所以我可以做到这一点"没说,但这似乎是至关重要的细节).请注意,使用相同的标识符创建第二个URL会话,系统可以将会话连接到它在另一个过程中为您执行的下载.第二个后台URL会话的目的仅仅是将委托与会话连接.
  • Save the background task in an instance variable on your extension delegate. We don’t want to set it complete just yet, but we need to keep it around to set complete later when the URL request finishes.
  • Create another background URL session using the background task’s sessionIdentifier, and use your extension delegate as the session’s delegate (why it doesn’t work to use another object as the delegate, I can’t say, but it seems to be a crucial detail). Note that using the same identifier to create a second URL session allows the system to connect the session to the download that it performed for you in another process; the purpose of this second background URL session is solely to connect the delegate with the session.

在会话委托中,同时实现urlSession(_ downloadTask: didFinishDownloadingTo:)urlSession(task: didCompleteWithError:)函数.

In the session delegate, implement both the urlSession(_ downloadTask: didFinishDownloadingTo:) and urlSession(task: didCompleteWithError:) functions.

与基于块的NSURLSessionDataTask不同,后台URL请求始终作为下载任务执行.系统执行该请求,并为您提供一个包含结果数据的临时文件.在urlSession(_ downloadTask: didFinishDownloadingTo:)函数中,该文件中的数据并根据需要进行处理以更新您的UI.

Unlike your block-based NSURLSessionDataTask, background URL requests are always performed as download tasks. The system performs the request and gives you a temporary file with the resulting data. In the urlSession(_ downloadTask: didFinishDownloadingTo:) function, the data in that file and process it as needed to update your UI.

最后,在代表的urlSession(task: didCompleteWithError:)函数中,调用setTaskCompletedWithSnapshot告诉系统您已经完成工作. ew.

Finally, in the delegate’s urlSession(task: didCompleteWithError:) function, call setTaskCompletedWithSnapshot to tell the system that you’ve finished your work. Phew.

正如我提到的那样,调试真的很令人沮丧,主要是因为这些事情实际发生时(如果它们确实发生)完全取决于系统. Apple的文档中有关于分配给后台刷新的预算的说法:

As I mentioned, this is all really frustrating to debug, mostly because it’s all up to the system when these things actually take place, if they happen at all. Apple’s documentation has this to say about the budget allocated to background refreshes:

通常,系统每小时为扩展坞中的每个应用程序(包括最近使用的应用程序)执行大约一项任务.该预算在扩展坞上的所有应用之间共享.该系统每小时为每个应用程序执行多项任务,并在活动表盘上进行复杂处理.该预算由表盘上的所有复杂功能分配.预算用完后,系统会延迟您的请求,直到有更多时间可用为止.

In general, the system performs approximately one task per hour for each app in the dock (including the most recently used app). This budget is shared among all apps on the dock. The system performs multiple tasks an hour for each app with a complication on the active watch face. This budget is shared among all complications on the watch face. After you exhaust the budget, the system delays your requests until more time becomes available.

最后一点要注意:传说中watchOS模拟器无法正确处理后台URL刷新任务,但不幸的是,Apple的文档对此没有官方的说法.如果可以的话,最好在Apple Watch硬件上进行测试.

One final note: legend has it that the watchOS simulator doesn’t handle background URL refresh tasks properly, but unfortunately, Apple’s docs have no official word on that. Best to test on Apple Watch hardware if you‘re able.

这篇关于安排并发症更新的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆