前言
我們都希望自己的app能流暢運行不掉幀乖菱,這個topic介紹了
- iOS12蘋果都做了哪些針對流暢度的優(yōu)化
- 相關的底層運行機制
- 怎樣高效地使用自動布局
- autolayout相關
iOS12的優(yōu)化
首先静陈,蘋果在iOS12對autolayout做了性能優(yōu)化,以下是幾個參數(shù)對比螟碎,灰色是iOS11眉菱,藍色是iOS12。
橫軸是時間掉分,可以看到Moving Tree的提升非常大俭缓,相信大家對于滑動列表時的卡頓(hiccup)都深有感觸克伊,以前我針對列表滑動這塊也做了不少優(yōu)化,這次更新iOS12后發(fā)現(xiàn)以前一些沒有做優(yōu)化的頁面华坦,有輕微卡頓的都不卡了愿吹,不過在一些性能敏感頁還是有卡頓現(xiàn)象,還是需要自己去優(yōu)化的惜姐。
Render Loop的運行原理
The render loop is the process that runs potentially at 120 times every second that make sure that all the content is ready to go for each frame.
———— by Ken Ferry
render loop是一個可以最多每秒運行120次的過程犁跪,用來保證每幀刷新前需要渲染的內(nèi)容都已經(jīng)準備好。
這個過程有三個階段歹袁,
先Update Constraints坷衍,再Layout,最后Display条舔。
Update Constraints從葉節(jié)點view開始執(zhí)行枫耳,直到window;
layoutSubviews則是反過來逞刷,從window開始嘉涌,傳遞到最終的葉節(jié)點view;
最后是drawRect繪制夸浅,也是從window開始仑最;
蘋果在設計上為了減少布局的重復調(diào)用,分了這3個階段帆喇,并提供了平行的類似功能的方法警医,比如updateConstraints和layoutSubviews,setNeedsUpdateConstraints和setNeedsLayout等
看到這里我感覺有必要重溫一下這些布局方法坯钦,這里就簡單回顧一下Update Constraints的3個方法
updateConstraints()
一般來說要改變某個約束预皇,我們可以在某個action或方法中改變約束就可以了,但我們也可以重寫某個view的updateConstraints婉刀,在重寫的updateConstraints里集中改變一批約束吟温。
updateConstraints通常是布局有變化時,系統(tǒng)給我們調(diào)用的突颊,也可以由setNeedsUpdateConstraints觸發(fā)鲁豪,由于調(diào)用在render loop的第一階段,他有兩個適用場景
- 當我們有許多約束要產(chǎn)生變化律秃,比如橫豎屏切換時爬橡,我們可以在traitCollectionDidChange里調(diào)用setNeedsUpdateConstraints,updateConstraints里集中處理需要變化的約束
- 當我們希望盡早產(chǎn)生這些變化
要注意的問題:
- 這個方法有可能每秒調(diào)用120次棒动,所以要避免churning constraints糙申,也就是不要deactivate所有約束,再重新activate他們船惨;只改變需要改變的約束柜裸,對于不變的約束只添加一次
- 方法內(nèi)不要調(diào)setNeedsUpdateConstraints缕陕,會產(chǎn)生feedback loop,雖然不是死循環(huán)但也會降低性能
- 在最后一行調(diào)用[super updateConstraints]疙挺,保證向父view傳遞
setNeedsUpdateConstraints()
這個方法用來標記當layout即將發(fā)生時榄檬,讓系統(tǒng)調(diào)用updateConstraints。
通常我們用它來優(yōu)化批量的約束變化衔统,這樣可以避免重復計算。調(diào)用后并不會馬上調(diào)updateConstraints海雪,而是在下一次render loop的layout即將發(fā)生時锦爵,調(diào)用updateConstraints
updateConstraintsIfNeeded()
當有l(wèi)ayout變化時,系統(tǒng)會調(diào)用這個方法奥裸,更新當前view和他的subviews的所有約束险掀,也可以由我們主動調(diào)用,來獲得最新的布局湾宙,和layoutIfNeeded類似樟氢。
禁止重寫這個方法。調(diào)用后也不會觸發(fā)updateConstraints侠鳄。
另外還提到了如果使用interface builder布局埠啃,能避免踩到一些降低性能的坑,但我覺得最好還是用純代碼更靈活些伟恶。
以下是會降低性能的坑:
好了灌灾,我們可以了解一下當激活約束時,到底發(fā)生了什么?
Update Phase
view通常是在一個window里缘揪,當update Constraint phase開始時, window會實例化一個布局引擎(layout engine)号杠,當給view添加約束時称簿,會將約束對應的等式(equation)
firstItem.firstAttribute {=,<=,>=} secondItem.secondAttribute * multiplier + constant
交給布局引擎,布局引擎通過代數(shù)運算解出view布局需要知道的相應變量的值即寒,如minX橡淆、minY、width蒿叠、height明垢。
其實就是最簡單的代數(shù)里的解方程。市咽。痊银。如下
解完后會通知view溯革,有value改變了贞绳,
view接著調(diào)用 [superView setNeedsLayout],讓自己進入layout phase致稀;
Layout Phase
view會將engine里計算好的variable值 copy到自己的frame冈闭,subview再調(diào)用setBounds,setCenter抖单,完成布局萎攒;
所以每一次在updateConstraints方法里deactivate constraints,再reactivate都會重復 實例化布局引擎-解方程-銷毀的過程矛绘,有可能每秒執(zhí)行多次耍休,從而導致性能問題;
正確的做法應該是這樣
如何高效地使用自動布局
don't pay for what you don't use
為了性能考慮货矮,不是同一個父view下的view是不應該相互產(chǎn)生約束關系的(雖然可以這么做)羊精,因為會顯著增加布局引擎的計算復雜度(類似于多個光源在多個物體上產(chǎn)生陰影),而目前iOS12里計算復雜度隨view增加是線性增長的囚玫;
所以也不要寫重復約束和沒有用的約束喧锦;
don't wedge two layouts into one set of constraints
在一個視圖里別搞兩套布局關系,如下圖
總結(jié):我們使用傳統(tǒng)的frame布局就是一個布局抓督、繪制燃少、渲染的過程,使用自動布局其實就多了一個布局引擎運算本昏。視頻里特別提到用約束來布局并不會比用frame布局消耗太多的性能供汛,因為engine的計算優(yōu)化了,過程很高效涌穆,所以不要避免使用autolayout怔昨,你可以頻繁地使用它,但不要添加多余或重復的約束宿稀。
和autolayout有關的東西
Inequality(也就是>=和<=)會消耗更多性能嗎趁舀,和等號相比非常小,只是多了一個變量而已祝沸;
setConstant 做了最大程度的優(yōu)化矮烹,鼓勵使用;
priority 只是告訴引擎一個minimize error罩锐,對性能影響不大奉狈;
Instrument增加了新的分析layout性能的工具(一片掌聲)
然而目前beta 版里沒有...
關于intrinsicContentsize
假如你寫死寬高仁期,不需要intrinsicContentsize,可以告訴引擎不用計算這個view的intrinsicContentSize,優(yōu)化性能跛蛋。
當然也可以返回一個指定的size熬的,都需要重寫,做法如下
關于systemLayoutSizeFittingSize
和intrinsicContentsize相反赊级,他是從布局引擎獲取size
每次調(diào)用該方法都要創(chuàng)建和銷毀一個布局引擎押框,所以比較耗性能,不應該頻繁調(diào)用理逊。
關于約束沖突(Unsatisfiable Constraints)
會降低性能橡伞,也可能掩蓋其他的布局問題,所以一定要解決晋被!