從視圖的創(chuàng)建到界面的優(yōu)化

前端的開發(fā),是離不開頁面的惩阶,那我們就需要了解挎狸,從最基本的視圖創(chuàng)建和繪制過程。
再來看一下断楷,當(dāng)前優(yōu)化的點(diǎn)有哪些锨匆,并且分析一下開源的幾種框架他們是怎么做的優(yōu)化。


UIView和CLAyer的關(guān)系

iOS開發(fā)中冬筒,內(nèi)容的顯示恐锣,是CLayer來做的,CLayer等同于一個(gè)紋理舞痰,紋理是GPU渲染的重要依據(jù)土榴;雖然我們一直操作的都是UIView,他們之間的關(guān)系是相互持有,UIView的一個(gè)屬性就是他內(nèi)部的CLayer响牛,而CLayer也通過delegate來持有UIView玷禽,設(shè)置frame/color這些都是在UIView內(nèi)部直接調(diào)用了layer.frame...,其結(jié)論就是呀打,UIView生成視圖樹论衍,我們操縱的是視圖樹,但是圖層樹也會(huì)同步添加或者刪除聚磺。

之前看過一篇文章坯台,寫為什么要設(shè)計(jì)成這樣;如果把設(shè)計(jì)的權(quán)交給問這個(gè)問題的人瘫寝,那么整個(gè)框架肯定變成了UILayer這種形式蜒蕾,既能接受事件稠炬、又能對外展示。但是隨著時(shí)間的推移咪啡,功能的添加和風(fēng)格的切換首启,我們會(huì)不斷的在這個(gè)UILayer類中添加和修改,很快整個(gè)類就無法維護(hù)和繼續(xù)了撤摸;所以在認(rèn)定展示是一個(gè)基本屬性后毅桃,作為根本,不需要太多的改變准夷,各自負(fù)責(zé)的功能也要遵守單一原則钥飞。文章地址


視圖的創(chuàng)建、修改到顯示的過程

首先衫嵌,由 app 處理事件(Handle Events)读宙,如:用戶的點(diǎn)擊操作,在此過程中 app 可能需要更新 視圖樹楔绞,相應(yīng)地结闸,圖層樹 也會(huì)被更新。

其次酒朵,app 通過 CPU 完成對顯示內(nèi)容的計(jì)算桦锄,如:視圖的創(chuàng)建、布局計(jì)算蔫耽、圖片解碼结耀、文本繪制等。在完成對顯示內(nèi)容的計(jì)算之后针肥,app 對圖層進(jìn)行打包饼记,并在下一次 RunLoop 時(shí)將其發(fā)送至 Render Server,即完成了一次 Commit Transaction 操作慰枕。

Render Server 主要執(zhí)行 Open GL具则、Core Graphics 相關(guān)程序(現(xiàn)在的渲染引擎改成Metal),并調(diào)用 GPU

GPU 則在物理層上完成了對圖像的渲染具帮。

最終博肋,GPU 通過 Frame Buffer、視頻控制器等相關(guān)部件蜂厅,將圖像顯示在屏幕上匪凡。


Core Animation不僅能做動(dòng)畫,還可以進(jìn)行渲染操作掘猿。
Core Graphics 2D的繪圖引擎病游,主要用于運(yùn)行時(shí)繪制圖像

commit經(jīng)過的是以下四個(gè)步驟
Lyout/Display/Prepare/Commit

Lyout 布局:設(shè)置 layer 的屬性,如 frame,background color 等衬衬,設(shè)置layer的層級(jí)信息买猖。layoutSubviews 在這一步調(diào)用
Display : 生成位圖,這一步會(huì)調(diào)用drawrect 方法滋尉;這一步有兩種情況需要區(qū)分玉控,我們要顯示的layer,有兩種方式可以設(shè)置顯示的位圖狮惜;
一高诺、系統(tǒng)默認(rèn)的,會(huì)在layer中創(chuàng)建一個(gè)backing store來存放生成的位圖碾篡。當(dāng)然虱而,系統(tǒng)的調(diào)用過程是給我們開了一個(gè)口子的,那就是drawRect,我們可以在這里邊使用UIKit或者Graphics進(jìn)行繪制一個(gè)要顯示的位圖耽梅。
二薛窥、我們可以攔截胖烛,自己繪制出位圖眼姐,直接設(shè)置到layer.cntents中。這一步很重要佩番,因?yàn)榇蟛糠诌M(jìn)行異步渲染的框架众旗,都是在這一步做的攔截,進(jìn)行自己的繪制趟畏。

Prepare :準(zhǔn)備提交的參數(shù)贡歧,包括動(dòng)畫的參數(shù);整個(gè)動(dòng)畫我們不需要做額外的操作赋秀,只需要起始和終止條件利朵,然后交給CA(Core Animation),他會(huì)在render server進(jìn)行計(jì)算猎莲,計(jì)算動(dòng)畫的中間狀態(tài)绍弟,然后重復(fù)完整個(gè)動(dòng)畫。
這一步還會(huì)進(jìn)行圖片的解碼操作著洼。

Commit:CA進(jìn)行數(shù)據(jù)的提交樟遣,遞歸提交。

在GPU參與以前身笤,都是CPU在進(jìn)行一系列的工作豹悬,所以優(yōu)化分為CPU約束型(CPU bound) 和 GPU約束型(GPU bound)。

針對上邊寫的流程液荸,從創(chuàng)建到渲染完成瞻佛,整個(gè)步驟包括commit的四步,render server中的繪制娇钱,還有就是到了GPU中的渲染

Lyout:
減少視圖的創(chuàng)建伤柄;使用輕量級(jí)的視圖CALyer代替UIView涡尘;盡量重用cell;

Display:
可以采用異步繪制的方式响迂,充分利用CPU的多核考抄。
GPU:

  • 減少Blending,并且使用沒有Alpha通道的圖片蔗彤;
  • 減少圖層的層級(jí)川梅;目前很多庫,都采用了繪制成一個(gè)位圖來GPU進(jìn)行渲染然遏。
  • 盡量避免離屏渲染贫途。

以上只是較大的要注意的地方,很多小的注意點(diǎn)在最后列出待侵。

關(guān)于離屏渲染的問題
我們認(rèn)為的關(guān)于CPU的離屏渲染丢早,可能會(huì)造成一些額外的開銷,但不是真正的離屏渲染秧倾,通過Xcode的Color Offscreen_Rendered Yellow怨酝,看下是不是變黃色。
離屏渲染發(fā)生在GPU上那先,他的性能消耗是在上下文的切換农猬;通常下邊的操作會(huì)引發(fā)離屏渲染:
1.cornerRadius+clipsToBounds 切圓角,如果兩個(gè)函數(shù)一起出現(xiàn)售淡,就會(huì)引發(fā)離屏渲染斤葱。
只有cornerRadius,表示只需要一個(gè)圓角揖闸,不需要將外圍切掉揍堕,不會(huì)引發(fā)離屏渲染。
優(yōu)化:可以使用Graphics提前繪制圓角的Texture汤纸,然后交個(gè)GPU衩茸。

  1. shadow 引發(fā)離屏渲染,通過通過shadowPath屬性可以避免蹲嚣。
    3.group opacity
  2. mask
  3. UIBlurEffect
    優(yōu)化:不用系統(tǒng)的模糊效果递瑰,另外實(shí)現(xiàn)CIGaussianBlur
    ....
    具體可以參考即刻

其他框架做的渲染優(yōu)化
美團(tuán)的Graver,采用了異步繪制 + 自己實(shí)現(xiàn)繪制的方式;
自己實(shí)現(xiàn)繪制就是將多個(gè)層級(jí)的layer隙畜,繪制到一個(gè)layer上抖部,將多層級(jí),降低為只有一個(gè)層級(jí)议惰,GPU渲染毫無壓力慎颗,不用進(jìn)行分層繪制了、也不會(huì)引發(fā)離屏渲染
具體方式是通過CoreText進(jìn)行l(wèi)ayer的繪制。
異步繪制:實(shí)現(xiàn)layer的displayer的代理方法俯萎,然后在這里邊進(jìn)行界面的異步繪制傲宜,繪制完回到主線程進(jìn)行顯示。

這地方稍微拓展一下layer的渲染過程夫啊,上邊說過函卒,分為兩種,系統(tǒng)自己的渲染和我們可以介入的方式撇眯。


參考圖來自其他博文

大部分的異步繪制报嵌,都是自己實(shí)現(xiàn)了displayerLayer這個(gè)代理方法,在這里進(jìn)行異步繪制熊榛,美團(tuán)的Graver就是攔截了這個(gè)函數(shù)使用Core Graphics 和 Core Text進(jìn)行繪制的锚国。

系統(tǒng)自己的渲染流程

自己渲染流程

會(huì)判斷l(xiāng)ayer有沒有delegate - > 有代理(也就是有UIView)并且實(shí)現(xiàn)了drawLayer:inContext 方法,就會(huì)進(jìn)行繪制玄坦,這個(gè)時(shí)候血筑,如果我們自己實(shí)現(xiàn)了drawReact ,會(huì)在drawLayer:inContext 方法中被直接調(diào)用。

VVeboTableViewDemo
這個(gè)思路是計(jì)算滑動(dòng)手指松開時(shí)的坐標(biāo)煎楣,優(yōu)先渲染周圍的幾個(gè)豺总。參考自保持頁面流暢的技巧

YYKit中文本的異步繪制也是在layer 的display函數(shù)中,直接進(jìn)行了異步繪制转质。他是自己實(shí)現(xiàn)了一個(gè)具有異步繪制功能的layer园欣,然后我們使用的時(shí)候帖世,可以將UILabel中內(nèi)部的layer換成這個(gè)休蟹,然后進(jìn)行Graphics的異步繪制,他做了一些優(yōu)化日矫,作者在他的博客中也提到了赂弓,控制并發(fā)線程的數(shù)量、及時(shí)取消不需要的繪制任務(wù)哪轿。

控制線程數(shù)量
控制線程數(shù)量這個(gè)通過他自建的queue 池盈魁,實(shí)現(xiàn)邏輯就是開辟線程的時(shí)候,他是通過串行隊(duì)列異步開啟窃诉,就等于有多少個(gè)串行隊(duì)列杨耙,就能開辟多少個(gè)線程。

及時(shí)取消任務(wù)
及時(shí)取消任務(wù)飘痛,他的解釋就是滑動(dòng)過快珊膜,很多任務(wù)其實(shí)不需要繪制了,就把不需要繪制的任務(wù)取消掉宣脉,每個(gè)layer都有一個(gè)標(biāo)記车柠,在異步繪制的時(shí)候,判斷當(dāng)前自己身上的標(biāo)記和最新的標(biāo)記比對,如果不一樣竹祷,直接返回谈跛。(上下滑動(dòng)過程中、設(shè)置字體等會(huì)觸發(fā)重繪)

監(jiān)控runloop周期進(jìn)行提交渲染
還有一個(gè)就是和Texture一樣的就是塑陵,監(jiān)測了runloop的周期感憾,在runloop的waiting和exit階段才進(jìn)行繪制提交(我們自己繪制的時(shí)候,設(shè)置contetnts令花,就會(huì)觸發(fā)CA的提交)吹菱,注意,這地方設(shè)置runloop的observer的時(shí)候彭则,優(yōu)先級(jí)是比系統(tǒng)的低鳍刷,系統(tǒng)的commit先提交完,再處理自定義的俯抖。

工欲善其事必先利其器
檢測工具的使用输瓜,我們在開發(fā)中,還有在線上芬萍,都需要對界面進(jìn)行調(diào)試和監(jiān)控尤揣。具體的監(jiān)控思想有以下幾種。

開發(fā):
Instruments肯定是比較理想的檢查工具柬祠,Core Animation..
關(guān)于離屏渲染北戏、視圖的混合都可以直接觀察(目前改變顏色這些直接在Xcode的debug工具里邊)
線上的時(shí)候:

  • 我們卡頓的檢測可以使用Runloop進(jìn)行狀態(tài)時(shí)間的檢測,添加observer進(jìn)行監(jiān)控漫蛔。
  • FPS的統(tǒng)計(jì)嗜愈,通過加入幀時(shí)間定時(shí)器,判斷FPS
  • 配合內(nèi)存的監(jiān)控莽龟,如果內(nèi)存過高蠕嫁,也將堆棧信息保存下來。
    以上幾種最好一起使用毯盈,因?yàn)镕PS獲取堆棧的時(shí)候剃毒,很可能抓取的是卡頓之后函數(shù)的堆棧信息。

微信的Matrix的思路
為了解決抓取調(diào)用棧不準(zhǔn)的問題搂赋,微信直接使用了另一種思路赘阀,每50毫秒抓取一次,保存最近的20個(gè)函數(shù)調(diào)用棧脑奠,發(fā)生卡頓后基公,判斷這20個(gè)調(diào)用棧中出現(xiàn)次數(shù)最多的那個(gè)調(diào)用棧,最耗時(shí)的方法就在里邊捺信。
這樣做會(huì)造成百分之3的CPU上升酌媒,這個(gè)微信團(tuán)隊(duì)自己測試過欠痴。
退火算法
先判斷每次抓取堆棧的hash是不是一樣,重復(fù)的不上傳秒咨,只上傳一次喇辽。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市雨席,隨后出現(xiàn)的幾起案子菩咨,更是在濱河造成了極大的恐慌,老刑警劉巖陡厘,帶你破解...
    沈念sama閱讀 212,884評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件仓手,死亡現(xiàn)場離奇詭異病蛉,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,755評論 3 385
  • 文/潘曉璐 我一進(jìn)店門雨女,熙熙樓的掌柜王于貴愁眉苦臉地迎上來墨叛,“玉大人石窑,你說我怎么就攤上這事炫欺。” “怎么了揉抵?”我有些...
    開封第一講書人閱讀 158,369評論 0 348
  • 文/不壞的土叔 我叫張陵亡容,是天一觀的道長。 經(jīng)常有香客問我冤今,道長闺兢,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,799評論 1 285
  • 正文 為了忘掉前任戏罢,我火速辦了婚禮屋谭,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘帖汞。我一直安慰自己戴而,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,910評論 6 386
  • 文/花漫 我一把揭開白布翩蘸。 她就那樣靜靜地躺著,像睡著了一般淮逊。 火紅的嫁衣襯著肌膚如雪催首。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 50,096評論 1 291
  • 那天泄鹏,我揣著相機(jī)與錄音郎任,去河邊找鬼。 笑死备籽,一個(gè)胖子當(dāng)著我的面吹牛舶治,可吹牛的內(nèi)容都是我干的分井。 我是一名探鬼主播,決...
    沈念sama閱讀 39,159評論 3 411
  • 文/蒼蘭香墨 我猛地睜開眼霉猛,長吁一口氣:“原來是場噩夢啊……” “哼尺锚!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起惜浅,我...
    開封第一講書人閱讀 37,917評論 0 268
  • 序言:老撾萬榮一對情侶失蹤瘫辩,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后坛悉,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體伐厌,經(jīng)...
    沈念sama閱讀 44,360評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,673評論 2 327
  • 正文 我和宋清朗相戀三年裸影,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了挣轨。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,814評論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡轩猩,死狀恐怖刃唐,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情界轩,我是刑警寧澤画饥,帶...
    沈念sama閱讀 34,509評論 4 334
  • 正文 年R本政府宣布,位于F島的核電站浊猾,受9級(jí)特大地震影響抖甘,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜葫慎,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,156評論 3 317
  • 文/蒙蒙 一衔彻、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧偷办,春花似錦艰额、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,882評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至废岂,卻和暖如春祖搓,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背湖苞。 一陣腳步聲響...
    開封第一講書人閱讀 32,123評論 1 267
  • 我被黑心中介騙來泰國打工拯欧, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人财骨。 一個(gè)月前我還...
    沈念sama閱讀 46,641評論 2 362
  • 正文 我出身青樓镐作,卻偏偏與公主長得像藏姐,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子该贾,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,728評論 2 351