保持复杂功能数据更新
Keep your complications up to date
2020年6月23日
一句话判断
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 的刷新预算