Keep your complications up to date
watchOS 进阶 20m

保持复杂功能数据更新

Keep your complications up to date

2020年6月23日

在 Apple 官方观看视频

一句话判断

Complication 数据刷新有严格的预算限制——这场 Session 讲了如何用有限的刷新次数让 Complication 始终显示最新数据,以及在什么时机触发刷新最高效。

这场 Session 讲了什么

Complication 的数据刷新不是无限次的。系统对每个 App 的 Complication 每天给予有限的刷新预算(大约 50 次),用完就不再刷新直到第二天重置。Session 详细讲解了如何在这个限制下高效管理数据更新。

核心 API 是 CLKComplicationServer.sharedInstance().reloadTimeline()extendTimeline()。前者重新请求整条 Timeline,后者在现有 Timeline 末尾追加数据。reloadTimeline 消耗一次预算,extendTimeline 也消耗一次预算,但后者不会丢弃未过期的条目。

Session 还介绍了后台刷新的调度机制:scheduleBackgroundRefresh() 可以在指定时间唤醒 App 来更新 Complication 数据,但同样受到预算限制。合理的时间线策略比频繁刷新更重要。

值得深挖的点

Timeline 策略决定了刷新效率。 如果你能提供一条覆盖全天的时间线(每小时一个 Entry),系统会在每小时自动切换到下一个 Entry,不需要消耗刷新预算。只有当数据发生意外变化时(如突然下雨),才需要调用 reloadTimeline。

reloadTimeline vs extendTimeline 的选择。 reloadTimeline 丢弃整条旧时间线并请求新的,适合数据发生根本性变化的场景(如用户切换了追踪的城市)。extendTimeline 在旧时间线末尾追加,适合需要延长时间线的场景(如活动还在进行中)。

代码片段

import ClockKit

// 在主 App 中触发 Complication 刷新
func updateComplications() {
    let server = CLKComplicationServer.sharedInstance()
    
    // 遍历所有活跃的 Complication 并刷新
    if let complications = server.activeComplications {
        for complication in complications {
            // 只有数据真正变化时才刷新,避免浪费预算
            if dataHasChanged(for: complication) {
                server.reloadTimeline(for: complication)
            }
        }
    }
}
// 构建高效的时间线——覆盖全天
func buildFullDayTimeline(for complication: CLKComplication) -> [CLKComplicationTimelineEntry] {
    let calendar = Calendar.current
    let now = Date()
    let startOfDay = calendar.startOfDay(for: now)
    
    // 每小时一个 Entry,覆盖 24 小时
    return (0..<24).compactMap { hour -> CLKComplicationTimelineEntry? in
        guard let entryDate = calendar.date(
            byAdding: .hour, value: hour, to: startOfDay
        ) else { return nil }
        
        let weather = fetchCachedWeather(for: entryDate)
        let template = createTemplate(for: complication, weather: weather)
        
        return CLKComplicationTimelineEntry(
            date: entryDate,
            complicationTemplate: template
        )
    }
}

// 在 Timeline Provider 中使用
func getTimeline(
    for complication: CLKComplication,
    withHandler handler: @escaping ([CLKComplicationTimelineEntry]?) -> Void
) {
    let entries = buildFullDayTimeline(for: complication)
    handler(entries)
}
// 使用后台刷新在关键时刻更新数据
func scheduleWeatherUpdate() {
    // 在下一次天气预报更新时间唤醒
    let nextUpdate = Calendar.current.date(
        byAdding: .hour, value: 3, to: Date()
    )!
    
    WKExtension.shared().scheduleBackgroundRefresh(
        withPreferredDate: nextUpdate,
        userInfo: nil
    ) { error in
        if let error = error {
            print("调度后台刷新失败: \(error)")
        }
    }
}

// 后台刷新回调
func handle(_ backgroundTasks: Set<WKRefreshBackgroundTask>) {
    for task in backgroundTasks {
        if task is WKApplicationRefreshBackgroundTask {
            // 刷新 Complication 数据
            updateComplications()
            // 调度下一次刷新
            scheduleWeatherUpdate()
            task.setTaskCompletedWithSnapshot(false)
        }
    }
}

最佳实践

  • 预生成足够长的时间线。 覆盖 24 小时只需要一次 reload,之后系统自动切换 Entry,不消耗额外预算。
  • 只在数据真正变化时调用 reload。 每次调用都消耗一次预算,不要在定时器里无脑刷新。
  • 后台刷新要形成链式调度。 每次后台刷新结束后立即调度下一次,确保持续更新。
  • 优先使用缓存数据构建时间线。 后台刷新时间有限,不要做耗时的网络请求,用缓存数据快速生成时间线。

还有什么值得关注

  • 系统在每天午夜重置刷新预算
  • WKExtension 的 scheduleBackgroundRefresh 有最小间隔限制
  • Complication 的 budgetRemaining 属性可以查看剩余刷新次数
  • watchOS 7 的多个 Complication 共享同一个 App 的刷新预算
WWDC 2020