安排复杂功能更新

2024-05-26

我在 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显示了进行此设置的最佳实践。简而言之:

  1. 您可以使用以下命令安排后台刷新WKExtension’s scheduleBackgroundRefresh就像你正在做的那样。
  2. 系统将在您首选日期之后的某个时间(由系统自行决定)唤醒您的分机WKRefreshBackgroundTask.
  3. 在您的扩展委托中handle方法,检查WKApplicationRefreshBackgroundTask;而不是使用URLSessionDataTask在这里,您需要安排一个背景URL 会话,以便系统可以挂起您的分机并代表您执行请求。请参阅WKURLSessionRefreshBackgroundTask https://developer.apple.com/documentation/watchkit/wkurlsessionrefreshbackgroundtask有关如何设置后台会话的详细信息,请参阅文档。
  4. 系统将在单独的进程中执行您的 URL 请求,并在完成后再次唤醒您的扩展程序。它将调用您的扩展代表handle方法和以前一样,这次使用WKURLSessionRefreshBackgroundTask。在这里,您需要做两件事:

    • 将后台任务保存在扩展委托的实例变量中。我们还不想将其设置为完整,但我们需要保留它以便稍后在 URL 请求完成时设置完成。
    • 使用后台任务创建另一个后台 URLsessionsessionIdentifier, and 使用您的扩展委托作为会话的委托(为什么使用另一个对象作为委托不起作用,我不能说,但这似乎是一个至关重要的细节)。请注意,使用相同的标识符创建第二个 URL 会话允许系统将会话连接到它在另一个进程中为您执行的下载;第二个后台 URL 会话的目的只是将委托与会话连接起来。
  5. 在会话委托中,实现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(使用前将#替换为@)

安排复杂功能更新 的相关文章

随机推荐

  • Ruby on Rails 4 不显示 content_tag :i

    为什么除了 content tag i 之外的所有 content tags 都显示 def sign full tag group obj name input type text size 12 popover true content
  • Cordova iOS 自定义插件:处理内存警告

    我正在开发一个使用 cordova 2 1 和一些自定义插件的 iOS 应用程序 我试图面对的问题如下 当我展示我的插件时 基本上是一个比内置插件具有更多功能的相机插件 cordova 插件 有时我会收到内存警告并随后卸载 包含 web 视
  • 在 HTML5 中设置视频高度

    也许这是一个简单的问题 但它真的让我发疯 我只想设置 HTML5 视频的高度和宽度 我正在使用这段代码
  • 如何在codeigniter中将数据写入.txt文件

    我的资产中有一个文件夹名称是login 我的疑问是如何设置路径 data id expense type amount exp date br todate date Y m d echo todate if write file asse
  • HTML5 最佳实践;节/标题/旁白/文章元素[关闭]

    Closed 这个问题是基于意见的 help closed questions 目前不接受答案 网络上 以及 stackoverflow 上 有足够的有关 HTML5 的信息 但现在我对 最佳实践 感到好奇 像节 标题 文章这样的标签是新的
  • 深入研究 JS 中的原型 [重复]

    这个问题在这里已经有答案了 我一直在尝试理解 JS 中原型的概念 但由于某种原因我发现它真的令人困惑 为什么以及何时使用原型 这有什么区别 从这个MDN 示例 https developer mozilla org en US docs J
  • Laravel 无法连接 Oracle

    我在用着耶吉拉 laravel oci8 https github com yajra laravel oci8用于 Oracle 与 Laravel 的连接 但我无法从客户端 PC 连接到 Oracle 服务器 showing this
  • ggplot2 中列组合的分面图

    我正在做相关性的组合 并且想在 ggplot2 中绘制每个组合 然而 我希望每个组合都在一个单独的面板上 而不是一个面板上的所有点 making up columns in my real data I m doing correlatio
  • 提供常量

    Provide 在 RC4 中已被弃用 以前 我可以这样做 provide API URL useValue address 我正在尝试这样的事情 provide API URL useValue address 但这不起作用 我找到了几个
  • 如何持久保存另一个应用程序提供的PendingIntent

    假设我想实现一个向其他应用程序公开服务的应用程序 例如 Google Play 服务 潜在的应用程序将注册与我的服务相关的特殊事件 并会在正确的时间收到通知 我正在考虑像 Google 对 Google Play 服务所做的那样来实现这一点
  • 如何显示证书的主题备用名称?

    我发现的最接近的答案是使用 grep gt openssl x509 text noout in cert pem grep DNS 有更好的方法吗 我只喜欢命令行 Thanks 较新版本的 openssl 有一个 ext 选项 允许您仅打
  • 从“Google 我的商家”获取 PHP 格式的营业时间

    我需要一些 PHP 代码来从我的 google 我的商家列表中提取我的营业时间 这在我的本地计算机上有效 但当我实时推送网站时不起作用 此代码停止我的 css 渲染并且不加载任何其他内容
  • php - 当存在 CDATA 时,将 xml 转换为 json 不起作用

    如果我使用以下php代码来转换xml to json I get Company fcsf Details n fgrtgrthtyfgvb n 但是 如果我使用CDATA in the Details元素如下
  • window.top.document.body.scrollTop 在 Chrome 或 FireFox 中不起作用

    我有下面的代码将打开一个模式窗口 这适用于 IE 8 但不适用于 Chrome 或 FF 我是跨浏览器功能领域的新手 function ShowModal WindowID FramesetID window onscroll functi
  • 如何使用带有“transfer-encoding: chunked”的 winhttp api

    我正在尝试将一些数据发送到需要 传输编码 分块 标头的网络服务 它可以很好地处理普通的 POST 请求 但一旦我添加标题 我总是会得到 由于以下情况 内容无法交付 收到客户端的无效请求 这是发送请求的部分 std vector
  • 不同保护条件下的状态转换

    在状态模式中这是如何建模的 当当前状态为 A 时 在触发器 X 和条件 C1 上状态 A 到状态 B 当当前状态为 A 时 在触发器 X 和条件 C2 上状态 A 到状态 C 这通常是如何实现的 我有很多可能需要实施的守卫条件 这是相当标准
  • Android Google Cloud Messaging (GCM) 和不匹配的发件人 ID

    我正在尝试在我的 Android 应用程序中使用 GCM 服务 为此 我使用了 android 文档http developer android com guide google gcm gcm html http developer an
  • 如何从前端使用 AWS CloudWatch Logs 提交简单日志?

    经过大约 1 小时的搜索 我没有找到任何有关 如何向 AWS CloudWatch Logs 提交简单日志 的信息从前端侧 几乎所有示例都是针对 Node js 的 但我需要从前端提交错误 而不是从后端提交错误 我什至没有找到应该用于前端的
  • 具有自定义集合属性的 JPA 投影

    我们正在使用 Spring Data 并尝试使用子查询创建自定义查询 结果投影有一个数组和其他属性 我们的问题在于子查询数组 public interface ProfesionalRepository extends JpaReposit
  • 安排复杂功能更新

    我在 Apple Watch 上有一个自定义复杂功能 我试图每小时更新一次 它应该每小时 ping 一个 API 端点 如果数据自上次检查以来发生了变化 则应更新复杂性 这是我目前所拥有的 似乎只有一次的效果 当它起作用时 它确实会 pin