Auto Layout前世今生
Auto Layout ,是蘋(píng)果公司提供的一個(gè)基于約束布局,動(dòng)態(tài)計(jì)算視圖大小和位置的庫(kù),并且已經(jīng)集成到了 Xcode 開(kāi)發(fā)環(huán)境里。
在引入 Auto Layout 這種自動(dòng)布局方式之前,iOS 開(kāi)發(fā)都是采用手動(dòng)布局的方式。而手動(dòng)布局的方式,原始落后、界面開(kāi)發(fā)維護(hù)效率低,對(duì)從事過(guò)前端開(kāi)發(fā)的人來(lái)說(shuō)更是難以適應(yīng)。所以,蘋(píng)果需要提供更好的界面引擎來(lái)提升開(kāi)發(fā)者的體驗(yàn),Auto Layout 隨之出現(xiàn)。但是在推出來(lái)之后,響應(yīng)卻平平,其原因就在于對(duì)其性能的擔(dān)憂(yōu)。即使后來(lái),蘋(píng)果公司推出了在 Auto Layout 基礎(chǔ)上模仿前端 Flexbox 布局思路的 UIStackView 工具,提高了開(kāi)發(fā)體驗(yàn)和效率,也無(wú)法解除開(kāi)發(fā)者們對(duì)其性能的顧慮。
iOS 12 將大幅提高 Auto Layout 性能,使滑動(dòng)達(dá)到滿(mǎn)幀。這不得不說(shuō)Cassowary 算法。
Cassowary布局算法
Cassowary 能夠有效解析線(xiàn)性等式系統(tǒng)和線(xiàn)性不等式系統(tǒng),用來(lái)表示用戶(hù)界面中那些相等關(guān)系和不等關(guān)系?;诖?,Cassowary 開(kāi)發(fā)了一種規(guī)則系統(tǒng),通過(guò)約束來(lái)描視圖間的關(guān)系。約束就是規(guī)則,這個(gè)規(guī)則能夠表示出一個(gè)視圖相對(duì)于另一個(gè)視圖的位置。由于 Cassowary 算法讓視圖位置可以按照一種簡(jiǎn)單的布局思路來(lái)寫(xiě),這些簡(jiǎn)單的相對(duì)位置描述可以在運(yùn)行時(shí)動(dòng)態(tài)地計(jì)算出視圖具體的位置。視圖位置的寫(xiě)法簡(jiǎn)化了,界面相關(guān)代碼也就更易于維于維護(hù)。蘋(píng)果公司也是看重了這一點(diǎn),將其引入到了自己的系統(tǒng)中。由于 Cassowary 算法本身的先進(jìn)性,更多的開(kāi)發(fā)者將Cassowary 運(yùn)用到了各個(gè)開(kāi)發(fā)語(yǔ)言中,比如 JavaScript、.NET、Java、Smalltalk、C++都有對(duì)應(yīng)的庫(kù)。
Auto Layout 的生命周期
Auto Layout 不只有布局算法 Cassowary,還包含了布局在運(yùn)行時(shí)的生命周期等一整套布局引擎系統(tǒng),用來(lái)統(tǒng)一管理布局的創(chuàng)建、更新和銷(xiāo)毀。了解 Auto Layout 的生命周期,是理解它的性能相關(guān)話(huà)題的基礎(chǔ)。
這一整套布局引擎系統(tǒng)叫作 Layout Engine ,是 Auto Layout 的核心,主導(dǎo)著整個(gè)界面布局。
每個(gè)視圖在得到自己的布局之前,Layout Engine 會(huì)會(huì)將視圖、約束、優(yōu)先級(jí)、固定大小通過(guò)計(jì)算轉(zhuǎn)換成最終的大小和位置。在 Layout Engine 里,每當(dāng)約束發(fā)生變化,就會(huì)觸發(fā) Deffered Layout Pass,完成后進(jìn)入監(jiān)聽(tīng)約束變化的狀態(tài)。當(dāng)再次監(jiān)聽(tīng)到約束變化,即進(jìn)入下一輪循環(huán)中。整個(gè)過(guò)程如下圖所示:
圖中, Constraints Change 表示的就是約束變化,添加、刪除視圖時(shí)會(huì)觸發(fā)約束變化。Activating 或 Deactivating,設(shè)置 Constant 或 Priority 時(shí)也會(huì)觸發(fā)約束變化。Layout Engine 在碰到約束變化后會(huì)重新計(jì)算布局,獲取到布局后調(diào)用 superview.setNeedLayout(),然后進(jìn)入 Deferred Layout Pass。
Deferred Layout Pass 的主要作用是做容錯(cuò)錯(cuò)處理。如果有些視圖在更新約束時(shí)沒(méi)有確定或缺失布局聲明的話(huà),會(huì)先在這里做容錯(cuò)處理。
接下來(lái),Layout Engine 會(huì)從上到下調(diào)用layoutSubviews() ,通過(guò) Cassowary算法計(jì)算各個(gè)子視圖的位置,算出來(lái)后將子視圖的 frame 從Layout Engine 里拷貝出來(lái)。
在這之后的處理,就和手寫(xiě)布局的繪制、渲染過(guò)程一樣了。所以,使用 Auto Layout 和手寫(xiě)布局的區(qū)別,就是多了布局上的這個(gè)計(jì)算過(guò)程。
Auto Layout 性能問(wèn)題
Auto Layout 在 iOS 12 中優(yōu)化后,它的性能得到了極大的提升,已經(jīng)基本和手寫(xiě)布局一樣可以達(dá)到性能隨著視圖嵌套的數(shù)量呈線(xiàn)性增長(zhǎng)了。而在此之前的 Auto Layout,視圖嵌套的數(shù)量對(duì)對(duì)性能的影響是呈指數(shù)級(jí)增長(zhǎng)的。使用 Auto Layout 一定要注意多使用 Compression Resistance Priority 和 Hugging Priority,利用優(yōu)先級(jí)的設(shè)置,讓布局更加靈活,代碼更少,更易于維護(hù)。
AutoLayout常見(jiàn)的問(wèn)題
(1)幾個(gè)更新方法的區(qū)別
- setNeedsLayout:告知頁(yè)面需要更新,但是不會(huì)立刻開(kāi)始更新。執(zhí)行后會(huì)立刻調(diào)用layoutSubviews。
- layoutIfNeeded:如果有需要刷新的標(biāo)記,立即調(diào)用layoutSubviews進(jìn)行布局;如果沒(méi)有標(biāo)記,不會(huì)調(diào)用layoutSubviews。如果希望立刻生成新的frame需要調(diào)用此方法,利用這點(diǎn)一般布局動(dòng)畫(huà)可以在更新布局后直接使用這個(gè)方法讓動(dòng)畫(huà)生效。
- layoutSubviews:對(duì)subviews進(jìn)行布局,不能主動(dòng)調(diào)用,需要的時(shí)候在子類(lèi)重寫(xiě),系統(tǒng)會(huì)在合適的時(shí)候自動(dòng)調(diào)用。
- 注意 : 如果要立即刷新frame,要先調(diào)用setNeedsLayout(),把標(biāo)記設(shè)為需要布局,然后馬上調(diào)用layoutIfNeeded(),實(shí)現(xiàn)布局。
- setNeedsUpdateConstraints:告知需要更新約束,但是不會(huì)立刻開(kāi)始
- updateConstraintsIfNeeded:告知立刻更新約束
- updateConstraints:系統(tǒng)更新約束
(2)系統(tǒng)調(diào)用layoutSubviews的時(shí)機(jī)
- init初始化不會(huì)觸發(fā)layoutSubviews,但是使用initWithFrame進(jìn)行初始化且rect不為zero時(shí),會(huì)調(diào)用layoutSubviews。
- addSubview的時(shí)候會(huì)觸發(fā)系統(tǒng)調(diào)用layoutSubviews。
- 當(dāng)view的frame發(fā)生改變的時(shí)候觸發(fā)layoutSubviews。
- 滾動(dòng)一個(gè)UIScrollView會(huì)觸發(fā)layoutSubviews。
- 旋轉(zhuǎn)Screen會(huì)觸發(fā)父UIView上的layoutSubviews事件。
- 改變一個(gè)UIView大小的時(shí)候也會(huì)調(diào)用父UIView上的layoutSubviews事件。
具體細(xì)節(jié)可以參考:http://www.code4app.com/blog-822415-3151.html
(3)什么時(shí)候使用frame布局,什么時(shí)候選用Auto Layout布局
簡(jiǎn)單的 UI 使用 Auto Layout ,復(fù)雜的 UI 使用 frame。原因如下:
- 從代碼量上來(lái)看,兩種布局方式相差不大。有時(shí)候發(fā)現(xiàn)復(fù)雜的 UI 使用 Auto Layout 的話(huà),代碼量反而會(huì)變多,因?yàn)閺?fù)雜的 UI 往往會(huì)有復(fù)雜的邏輯,比如根據(jù)數(shù)據(jù)的不同,部分 UI 的顯示會(huì)有變動(dòng)(比如某個(gè)子視圖隱藏與顯示, 會(huì)影響到其它視圖的布局)。
- 固定的UI簡(jiǎn)單的布局,這種情況下使用 Auto Layout 還是挺方便的,具有快速、方便、簡(jiǎn)潔的布局效果。
- 動(dòng)態(tài)復(fù)雜的 UI 布局,這種情況下使用 Auto Layout 來(lái)布局,感覺(jué)就不合適。因?yàn)椴还苁?frame 還是 Auto Layout,都需要去計(jì)算高度,Auto Layout通過(guò) Cassowary 算法計(jì)算各個(gè)子視圖的位置,算出來(lái)后將子視圖的 frame 從 Layout Engine 里拷貝出來(lái);而frame布局,則可以快速的通過(guò)事先約定的布局計(jì)算出相應(yīng)的frame,再進(jìn)行相應(yīng)的繪制、渲染。這種情況下,直接使用 frame 會(huì)比較精簡(jiǎn)。