如何編寫高性能的 Auto Layout

對於常常寫 UI 頁面的 iOS 開發者來講,Auto Layout 是提升開發效率的一大利器。但若是使用不當的話,也會對項目性能形成損害。因此,知道如何正確的使用 Auto Layout 仍是很重要的。markdown

本文首先會經過一個例子來講明下什麼是約束流失,瞭解約束流失後,會帶你們看一下 Render Loop 的工做流程。而後會說下 Auto Layout 的背後實現原理。最後,瞭解下 Auto Layout 特定狀況下的最佳作法。讓咱們開始吧。ide

約束流失

什麼是約束流失?oop

答:對於同一視圖,進行沒必要要的刪除和從新添加約束。佈局

經過下面的例子來解釋一下:性能

var myConstraints = [NSLayoutConstraint]()
let text1 = UILabel()
let text2 = UILabel()

override func updateViewConstraints() {
    // step1
    NSLayoutConstraint.deactivate(myConstraints)
    myConstraints.removeAll()
    // step2
    let views = ["text1": text1, "text2": text2]
    myConstraints += NSLayoutConstraint.constraints(withVisualFormat: "H:|-[text1]-[text2]",
                                                    options: [.alignAllFirstBaseline],
                                                    metrics: nil,
                                                    views: views)
    
    myConstraints += NSLayoutConstraint.constraints(withVisualFormat: "V:|-[text1]-|",
                                                    options: [.alignAllFirstBaseline],
                                                    metrics: nil,
                                                    views: views)
    // step3
    NSLayoutConstraint.activate(myConstraints)
    super.updateViewConstraints()
}
複製代碼

上面的代碼作了三件事:spa

  • step1:將以前的約束失效並移除。
  • step2:從新設置約束。
  • step3:生效從新設置的約束。

移除,從新建立。直觀上看這不是高效的代碼,事實上也確實不是。它會給性能帶來壓力,由於這段代碼會每秒執行不少次。code

咱們能夠經過一個簡單的 if 判斷來避免無用的執行。orm

override func updateViewConstraints() {
    if myConstraints.isEmpty {
        var constrains = [NSLayoutConstraint]()
        
        let views = ["text1": text1, "text2": text2]
        constrains += NSLayoutConstraint.constraints(withVisualFormat: "H:|-[text1]-[text2]",
                                                        options: [.alignAllFirstBaseline],
                                                        metrics: nil,
                                                        views: views)
        
        constrains += NSLayoutConstraint.constraints(withVisualFormat: "V:|-[text1]-|",
                                                        options: [.alignAllFirstBaseline],
                                                        metrics: nil,
                                                        views: views)
        NSLayoutConstraint.activate(constrains)
        myConstraints = constrains
    }
    super.updateViewConstraints()
}
複製代碼

在更新約束的時候,首先判斷當前約束是否有值,如有值則直接跳過;無值再建立約束賦值。這樣就避免了屢次執行移除,從新建立的流程。開發

Render Loop 概覽

Render Loop 涉及更新約束、佈局、顯示三個階段。下面是這三個階段的流向: 截屏2021-05-11 下午3.23.31.pngrem

更新佈局是從底層視圖一步步流向 window ,而佈局恰巧相反,從 window 流向底層視圖,顯示則和佈局流向一致。

Render Loop 的優勢:能夠避免無用的工做;注意事項:會運行不少次。因此咱們應該謹慎使用。

Auto Layout 的背後實現

當咱們給控件添加約束時,下面的四個值必須能計算出來,不然視圖會顯示不正常。

控件需計算的四個值:minX、minY、width、height。

截屏2021-05-11 下午3.32.38.png

好比上面的約束會替換成下面的公式:

  • text1
    • text1.minX = 20
    • text1.minY = 30
    • text1.width = 100
    • text1.height = 20
  • text2
    • text2.minX = text1.minX + text1.width + 10 最終得出 130
    • text2.minY = 30
    • text2.width = 100
    • text2.height = 20

也就是說 Auto Layout 就是用二元一次方程式來求出各個參數的值。

當咱們寫下上面的約束時,系統會建立一個 Engine,Engine 去負責約束的計算,最終 Engine 會把 minX、minY、width、height 的具體值返回給 View,View 則根據返回值調用 setNeedsLayout() 來更新視圖。

截屏2021-05-11 下午3.49.31.png

最佳作法

  • 特定狀況下須要隱藏控件
    • 直接使用 hidden 屬性就好,不要移除控件或者約束。
  • 刷新視圖
    • 避免移除全部約束,最好在當前約束的基礎上修改。
    • 對於不變的約束確保只添加一次。
    • 只修改須要改變的約束。

總結

  • 不要讓約束流失。
  • 約束底層計算只是簡單的方程式計算。
  • 只爲你的功能耗費性能,不作無謂的消耗。
  • 避免添加有歧義的約束,好比要求 view 的 width 便是 50 又是 200。
相關文章
相關標籤/搜索