引言
前面的章節學完已經讓我們可以順利實現一個小組件了,但是小組件里面的數據如何刷新的呢,本節內容將講解IOS的刷新機制。
大綱
- 系統如何管理小組件刷新
- Timeline刷新機制
- Timeline刷新機制代碼實現
- 刷新策略建議
- 時鍾刷新策略(只有小時分鍾,沒有秒)
- 主動請求重新刷新
系統如何管理小組件刷新
- WidgetKit在一個單獨的進程中渲染小組件視圖
- 即使小組件窗口顯示在屏幕上,widget extension 也不會持續處於活動狀態
- 為了管理系統負載,WidgetKit使用預算來分配一天中的窗口小組件重載
- WidgetKit為用戶添加到其設備的每個活動小組件維護不同的預算
- 每日預算通常包括40到70次刷新。該速率大致可轉換為小組件每15至60分鍾重新加載一次,但是由於涉及到許多因素,因此這些時間間隔是不固定的。
綜上所述,小組件的刷不能由開發者自由控制,官方建議如下:
- 如果您的小組件可以預測應重新加載的時間點,則最好的方法是為盡可能多的將來日期生成時間線。
- 時間軸中的條目間隔應保持盡可能大。
- 時間軸應創建至少相隔5分鍾的時間軸條目。
- WidgetKit可能會在多個窗口小組件之間合並重新加載,從而影響窗口小組件重新加載的確切時間。
Timeline刷新機制
該圖顯示了WidgetKit請求時間線,提供程序生成時間線以及3小時后的時間進度,之后WidgetKit請求新的時間線
該圖顯示了WidgetKit請求時間線,提供程序生成時間線以及WidgetKit在2小時后請求新時間線的圖
Timeline刷新機制代碼實現(新增組件時,系統默認就實現了)
func getTimeline(for configuration: TimeTypeConfigurationIntent, in context: Context, completion: @escaping (Timeline<Entry>) -> ()) {
var entries: [SimpleEntry] = []
// Generate a timeline consisting of five entries an hour apart, starting from the current date.
let currentDate = Date()
for hourOffset in 0 ..< 5 {
// 下面這個代碼表示,在當前日期上加上 hourOffset 個小時得到一個新的日期
// .hour可以換成 .second .minute .day 等
let entryDate = Calendar.current.date(byAdding: .hour, value: hourOffset, to: currentDate)!
let entry = SimpleEntry(date: entryDate, configuration: configuration)
entries.append(entry)
}
// 調用回調方法把生成好的時間線數據傳遞給系統
// policy 表示刷新策略
// .atEnd 表示,所有的時間線條目完成之后重新刷新一次,表現就是這個getTimeline方法被回調一次
// .after(date: Date) 表示,多久時間結束后再刷新一次
// .never表示時間軸走完就不刷了
let timeline = Timeline(entries: entries, policy: .atEnd)
completion(timeline)
}
備注:
網上大部分資料都寫着Timeline時間軸相隔5分鍾,即每次創建5分鍾內的刷新條目,但是小組件預算每日40到70次刷新,假設按70次算,總時間70 * 5 = 350分鍾,大約6個小時就把次數用完了。所以大部分情況5分鍾的間隔確實可以滿足了,但是難免還是有用戶把這個限制次數用完了。保險起見,盡量把時間間隔擴大,如果內存消耗不大,可以把間隔控制在60分鍾,時間軸上每個條目間隔1分鍾。這樣幾乎不會把系統給小組件的預算刷新次數給用完。
正是因為IOS系統對小組件有刷新次數有限制和內存方面的限制(官網沒有找到,但是看到網友們說是30M左右的限制,自己使用過程中也發現了占用內存過多導致進程被掛起,小組件就展示不出來了),所以沒控制好刷新策略的話,可能經常會出現小組件界面展示不出來,或者過了一段時間之后,小組件直接不刷新了。
刷新策略建議
- 每次刷新時,時間軸准備好15-60分鍾的刷新數據,最少是5分鍾
- 時間軸每個刷新條目時間間隔盡可能大,時鍾內組件間隔可以設置為1分鍾
- 條目數量不宜過多,越少越好,時鍾組件最多60左右
- 不要在5分鍾內創建300個條目來做時鍾按秒刷新,大概率會失敗
時鍾刷新策略(只有小時分鍾,沒有秒)
static func prepareEntriesEveryMinute(_ completion: @escaping (Timeline<WidgetEntry>) -> ()) {
// 第一次刷新時間:延遲2秒刷
let firstDate = Provider.getFirstEntryDate()
// 第二次刷新時間:第一個整分鍾時刷
let firstMinuteDate = Provider.getFirstMinuteEntryDate()
var entries: [WidgetEntry] = []
entries.append(WidgetEntry(date: firstDate))
entries.append(WidgetEntry(date: firstMinuteDate))
// 后面以第一個整點分鍾開始,每次加一分鍾刷
for minuteOffset in 1 ..< 60 {
guard let entryDate = Calendar.current.date(byAdding: .minute, value: minuteOffset, to: firstMinuteDate) else {
continue
}
entries.append(WidgetEntry(date: entryDate))
}
let timeline = Timeline(entries: entries, policy: .atEnd)
completion(timeline)
}
static func getFirstEntryDate() -> Date {
let offsetSecond: TimeInterval = TimeInterval(2)
var currentDate = Date()
currentDate += offsetSecond
return currentDate
}
// 獲取第一個分鍾時間點所處的時間點
static func getFirstMinuteEntryDate() -> Date {
var currentDate = Date()
let passSecond = Calendar.current.component(.second, from: currentDate)
let offsetSecond: TimeInterval = TimeInterval(60 - passSecond)
currentDate += offsetSecond
return currentDate
}
主動請求重新刷新
如果在App中修改了小組件的數據,可以通過如下的方式主動觸發WidgetKit刷新小組件。
// 指定刷新哪個組件
WidgetCenter.shared.reloadTimelines(ofKind: "com.mygame.character-detail")
// 刷新全部組件
WidgetCenter.shared.reloadAllTimelines()
結語
小組件的刷新,官方文檔都沒有明確說明到底是什么具體的規則,只說了有各種限制,系統會動態管理。所以在實際開發中可能會遇到小組件數據不刷新的問題,遇到這種情況,請減少Timeline中的條目數量,優化內存,確保小組件代碼里面沒有異常。小組件運行在單獨的進程,如果異常會導致小組件進程卡死了,一個小組件出問題,其他小組件都不刷新了。既然刷新這么難控制,怎么實現數字時鍾按秒刷新呢?下一節揭曉。