01.iOS 12 的改善
UICollectionView性能對比,item自動適配大小,iOS 11看上去有掉幀卡頓的現象,iOS 12表現完美,沒有掉幀。
下面是iOS 11和iOS 12的性能對比,灰色條是iOS 11的耗時,藍色條是iOS 12的耗時。在iOS 12上會很大程度改善你的應用程序。
02.實現和感觀
render loop
render loop 是一個每秒鐘跑120次的一個進程,是為了確保所有的內容都能為每一個frame做好準備。lender loop 一共包括三個步驟來更新約束,佈局和渲染。
首先,每一個需要接收到更新約束的view會從子view向上傳遞,直到window
然後,每一個接收到的view開始layoutsubviews,和更新約束是從相反的方向開始,layout從window開始到每一個子view進行layout。
最後,每一個需要渲染的view,和layout相同,從父view向子view開始渲染。
render loop目的是為了避免重複的工作。
舉一個例子:一個UILable 需要一個約束來描述它的大小,但是有很多屬性會影響他的大小,設置它的font,text size等等都會受到影響。當一個屬性改變的時候,可能text其他屬性也會被重新賦值,很有可能調用一堆屬性的setter方法,這樣效率會很低。
只需要調用updateConstraints 並指定好要更新的屬性,render loop會幫助你計算好它的frame並完成渲染,從而避免多次設置的重複工作。
在設置約束的一些不好的寫法,每次開始的時候調用deactivate,設置結束之後調用activate。相當於layoutsubviews,每次調用layoutsubviews你銷燬你subviews,重新創建在重新添加。這樣性能不會很好。
每次都是移除並重新添加,相當於這樣的代碼
官方建議寫法為,約束只需要添加一次,每次調用super.updateConstraints完成約束的更新。
render loop有很強的特定性,它的好處可以避免一些重複性的工作。但是它也很危險,因為它調用的頻率會很高,是非常敏感的一段代碼。
蘋果建議使用interface builder進行佈局。
激活一個約束
在設置約束的時候發生了什麼事情呢?從下面的圖中可以看到整體的一個結構。
有一個view 在window上,window上面有個叫做engine的內部對象,engine是autolayout計算的核心,當添加一個約束的時候,會創建一個Equation對象,然後會把equation對象添加到engine上,equation依據variables對象。
variables相當於每一個約束的值,比如說一個UIlabel有四個約束minX minY width height那麼minX minY width height 就是variables。
個圖為例,這裡只關注水平方向的佈局,首先要創建equation,然後每一個equation會添加給engine。
engine會去計算這些variables,engine會把每一個view的variables用數學公式計算出一個定量。
計算出定量之後,engine會發送通知,通知view調用他父view的setNeedsLayout()方法,就會完成render loop的第一步更新約束,然後繼續render loop的 layout更新,最後view會直接拷貝engine計算好的定量進行賦值渲染。
engine是一個layout的緩存,和依賴的追蹤器。非常有方向性的,它知道哪些約束會影響哪些view,當你改變一些約束時,它能夠準確的更新。
不需要的約束不要加
你也可以穿過層級,為兩個沒有相同父view的view設置約束,但是這樣性能會很差。
大多數情況下,view的約束應該加在他的父view或者兄弟view上。
最小限度的錯誤
當view向engine獲取約束的值的時候,engine會確保錯誤率最小
03.構建高性能layout
創建一個layout
構建一個社交軟件的cell,通過autolayout進行佈局。
查找代碼中的問題
下面是beta版的一個調試工具,最上面第一項表示你CPU的使用情況,峰值的地方可能需要關注一下你的layout是否有性能問題,下面一行追蹤你的約束,高的地方說明是有問題的。
第二項是你對約束添加、刪除、修改等操作的記錄。
第三項是當前控件的大小。
點擊約束峰值的地方可以看詳情。
創建高性能的佈局
通過instrument調試工具,可以看出一些佈局上的耗時問題。一下是需要注意的幾點:
1.避免刪除所有的約束的情況
2.對於靜態約束,只需要添加一次
3.只改變需要改變的約束
4.儘量用hide() 方法隱藏view,而不是remove然後在add
有些控件比較特殊,比如 UIImageView,它的大小是根據他的image計算確定他的content size。UILabel是根據他的text確定的。這些都會返回它們的固有尺寸,UIView 會直接通過他們的固有尺寸來當做約束條件。
重寫 intrinsicContentSize
text的計算是成本很高的,所以UIlabel的size通過text去控制計算開銷成本會很高。這個時候我們可以 通過重寫 UILabel 的 intrinsicContentSize 來直接控制它的固有尺寸。如果已知一個UILabel的展示size,直接重寫其屬性,其他情況使用UIView.noIntrinsicMetric。
參考:WWDC2018《High Performance Auto Layout》
閱讀更多 今日頭條技術團隊 的文章