IOS Widget(5):小元件重新整理機制

popfisher發表於2021-05-10

引言

  前面的章節學完已經讓我們可以順利實現一個小元件了,但是小元件裡面的資料如何重新整理的呢,本節內容將講解IOS的重新整理機制。

大綱

  • 系統如何管理小元件重新整理
  • Timeline重新整理機制
  • Timeline重新整理機制程式碼實現
  • 重新整理策略建議
  • 時鐘重新整理策略(只有小時分鐘,沒有秒)
  • 主動請求重新重新整理

系統如何管理小元件重新整理

  1. WidgetKit在一個單獨的程式中渲染小元件檢視
  2. 即使小元件視窗顯示在螢幕上,widget extension 也不會持續處於活動狀態
  3. 為了管理系統負載,WidgetKit使用預算來分配一天中的視窗小元件過載
  4. WidgetKit為使用者新增到其裝置的每個活動小元件維護不同的預算
  5. 每日預算通常包括40到70次重新整理。該速率大致可轉換為小元件每15至60分鐘重新載入一次,但是由於涉及到許多因素,因此這些時間間隔是不固定的。

綜上所述,小元件的刷不能由開發者自由控制,官方建議如下:

  1. 如果您的小元件可以預測應重新載入的時間點,則最好的方法是為儘可能多的將來日期生成時間線。
  2. 時間軸中的條目間隔應保持儘可能大。
  3. 時間軸應建立至少相隔5分鐘的時間軸條目。
  4. 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左右的限制,自己使用過程中也發現了佔用記憶體過多導致程式被掛起,小元件就展示不出來了),所以沒控制好重新整理策略的話,可能經常會出現小元件介面展示不出來,或者過了一段時間之後,小元件直接不重新整理了。

重新整理策略建議

  1. 每次重新整理時,時間軸準備好15-60分鐘的重新整理資料,最少是5分鐘
  2. 時間軸每個重新整理條目時間間隔儘可能大,時鐘內元件間隔可以設定為1分鐘
  3. 條目數量不宜過多,越少越好,時鐘元件最多60左右
  4. 不要在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中的條目數量,優化記憶體,確保小元件程式碼裡面沒有異常。小元件執行在單獨的程式,如果異常會導致小元件程式卡死了,一個小元件出問題,其他小元件都不重新整理了。既然重新整理這麼難控制,怎麼實現數字時鐘按秒重新整理呢?下一節揭曉。

相關文章