對於常常寫 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
移除,從新建立。直觀上看這不是高效的代碼,事實上也確實不是。它會給性能帶來壓力,由於這段代碼會每秒執行不少次。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 涉及更新約束、佈局、顯示三個階段。下面是這三個階段的流向: rem
更新佈局是從底層視圖一步步流向 window ,而佈局恰巧相反,從 window 流向底層視圖,顯示則和佈局流向一致。
Render Loop 的優勢:能夠避免無用的工做;注意事項:會運行不少次。因此咱們應該謹慎使用。
當咱們給控件添加約束時,下面的四個值必須能計算出來,不然視圖會顯示不正常。
控件需計算的四個值:minX、minY、width、height。
好比上面的約束會替換成下面的公式:
也就是說 Auto Layout 就是用二元一次方程式來求出各個參數的值。
當咱們寫下上面的約束時,系統會建立一個 Engine,Engine 去負責約束的計算,最終 Engine 會把 minX、minY、width、height 的具體值返回給 View,View 則根據返回值調用 setNeedsLayout()
來更新視圖。