iOS UI繪制理論基礎(chǔ)

一、UIView如何顯示內(nèi)容

當(dāng)我們操作UI時窄做,例如改變frame队询、更新UIView/CALayer,或者自己去調(diào)用setNeedsLayout/setNeedsDisplay方法泼差,UIView會調(diào)用-[CALayer setNeedsLayout]/-[CALayer setNeedsDisplay]方法贵少,給layer上打上一個臟標(biāo)記,意味著需要重繪堆缘。但是只有在下一次runloop即將結(jié)束的時候才會調(diào)用[CALayer display],而這個方法會判斷是否實現(xiàn)了displayLayer這個方法滔灶,如果沒有實現(xiàn),那么走系統(tǒng)調(diào)用吼肥,如果實現(xiàn)了就為我們提供了異步繪制的入口录平。具體可以參看下面的流程圖

繪制流程

系統(tǒng)繪制:

系統(tǒng)繪制

我們首先看一下系統(tǒng)繪制,當(dāng)[CALayer dispaly]方法調(diào)用的時候缀皱,他會檢查-dispalyerLayer方法是否被實現(xiàn)了斗这,若沒有實現(xiàn)則我們調(diào)用系統(tǒng)的繪制方法。首先 CALayer會生成一個backing store(CGContextRef)啤斗,每個layer都有一個content表箭,這個content指向的一塊緩存稱為backing store。如果layer有delegate争占,則調(diào)用delegate的- (void)drawLayer:(CALayer *)layer inContext:(CGContextRef)ctx方法燃逻,否則調(diào)用-[CALayer drawInContext:]方法,進而調(diào)用[UIView drawRect:]方法序目。
UIKit會將這個conext推到系統(tǒng)的context堆棧中,如果在draw rect中通過UIGraphicsGetCurrentContext() 取得的CGContextRef就是CALayer生成的這個實例伯襟。所有的繪制操作也會在這塊Context上生效猿涨。
CPU 執(zhí)行完draw rect之后,通過context將數(shù)據(jù)寫入backing store姆怪。當(dāng)backing store寫完之后叛赚,通過rendserver交給GPU去渲染,將backing store中的bitmap數(shù)據(jù)顯示在屏幕上稽揭。

二俺附、UIKit遇到的問題

iOS的mainLoop是一個60fps的回掉,即16.7毫秒繪制一次屏幕溪掀。這個時間內(nèi)要完成view的緩沖區(qū)創(chuàng)建事镣,view內(nèi)容的繪制,這些是cpu的工作揪胃,cpu完成之后交給GPU去渲染璃哟,這個過程又包含了多個view的拼接,紋理的渲染喊递,最終渲染在屏幕上随闪。如果這個時候,cpu做了很多工作骚勘,view層次過于復(fù)雜铐伴,圖片過大,導(dǎo)致gpu壓力也很大俏讹,那么就會出現(xiàn)迪奧幀的現(xiàn)象当宴,也就是表現(xiàn)在我們的眼里的“卡”。
如果我們所有的繪制任務(wù)都交給UIKit去做藐石,因為UIKit不是線程安全的即供,所以官方也建議我們只在主線程操作定拟。那么就無法利用cpu多核的優(yōu)勢于微,無法異步的進行繪制,但是通過對UIView繪制原理的了解我們知道青自,在異步繪制是有他的理論基礎(chǔ)的株依。

三、異步繪制的原理

好延窜,我們現(xiàn)在說一下異步繪制的原理恋腕。
我們不能在非主線程將內(nèi)容繪制到layer的context上,但是我們可以將需要繪制的內(nèi)容繪制在一個自己創(chuàng)建的跑private_context上逆瑞。通過CGBitmapContextCreate()可以創(chuàng)建一個CGCentextRef荠藤,在異步線程使用這個context進行繪制伙单,最后通過CGBitmapContextCreateImage()創(chuàng)建一個CGImageRef,并在主線程設(shè)置給layer的contents,完成異步繪制哈肖。

- (void)display {
    dispatch_async(backgroundQueue, ^{
        CGContextRef ctx = CGBitmapContextCreate(...);
        // draw in context...
        CGImageRef img = CGBitmapContextCreateImage(ctx);
        CFRelease(ctx);
        dispatch_async(mainQueue, ^{
            layer.contents = img;
        });
    });
}

四吻育、CPU相關(guān)優(yōu)化

view的繪制與CPU和GPU都有很強的關(guān)系,但是具體是哪些呢淤井?了解了之后我們才能為卡頓問題的優(yōu)化提成更好的解決方案布疼。

4.1創(chuàng)建對象

對象的創(chuàng)建會分配內(nèi)存、調(diào)整屬性币狠、甚至?xí)赡苡蠭/O操作游两,比較消耗CPU資源。所以要盡量用輕量的對象代替重量的對象漩绵,可以對性能有所優(yōu)化贱案。比如 CALayer 比 UIView 要輕量,如果不需要響應(yīng)觸摸事件止吐,用 CALayer 顯示會更加合適轰坊。如果對象不涉及 UI 操作,則盡量放到后臺線程去創(chuàng)建祟印,但如果是包含了 CALayer 的控件肴沫,都只能在主線程創(chuàng)建和操作。盡量不使用storyboard創(chuàng)建視圖對象蕴忆。使用懶加載颤芬,將不重要對象的創(chuàng)建時機延后。

4.2調(diào)整對象視圖層級

對象的視圖層級變化也會增加cpu的運算套鹅,應(yīng)盡量減少addsubview站蝠,removesubview等操作。減少視圖的層級卓鹿,避免過多的調(diào)整菱魔。

4.3調(diào)整對象布局

視圖布局的計算是 App 中最為常見的消耗 CPU 資源的地方。如果能在后臺線程提前計算好視圖布局吟孙、并且對視圖布局進行緩存澜倦,那么這個地方基本就不會產(chǎn)生性能問題了。

不論通過何種技術(shù)對視圖進行布局杰妓,其最終都會落到對 UIView.frame/bounds/center 等屬性的調(diào)整上藻治。上面也說過,對這些屬性的調(diào)整非常消耗資源巷挥,所以盡量提前計算好布局桩卵,在需要時一次性調(diào)整好對應(yīng)屬性,而不要多次、頻繁的計算和調(diào)整這些屬性雏节。

4.4文本計算胜嗓、文本渲染

如果一個界面中包含大量文本(比如微博微信朋友圈等),文本的寬高計算會占用很大一部分資源钩乍,并且不可避免兼蕊。如果你對文本顯示沒有特殊要求,可以參考下 UILabel 內(nèi)部的實現(xiàn)方式:用 [NSAttributedString boundingRectWithSize:options:context:] 來計算文本寬高件蚕,用 -[NSAttributedString drawWithRect:options:context:] 來繪制文本孙技。盡管這兩個方法性能不錯,但仍舊需要放到后臺線程進行以避免阻塞主線程排作。

如果你用 CoreText 繪制文本牵啦,那就可以先生成 CoreText 排版對象,然后自己計算了妄痪,并且 CoreText 對象還能保留以供稍后繪制使用哈雏。
屏幕上能看到的所有文本內(nèi)容控件,包括 UIWebView衫生,在底層都是通過 CoreText 排版裳瘪、繪制為 Bitmap 顯示的。常見的文本控件 (UILabel罪针、UITextView 等)彭羹,其排版和繪制都是在主線程進行的,當(dāng)顯示大量文本時泪酱,CPU 的壓力會非常大派殷。對此解決方案只有一個,那就是自定義文本控件墓阀,用 TextKit 或最底層的 CoreText 對文本異步繪制毡惜。盡管這實現(xiàn)起來非常麻煩,但其帶來的優(yōu)勢也非常大斯撮,CoreText 對象創(chuàng)建好后经伙,能直接獲取文本的寬高等信息,避免了多次計算(調(diào)整 UILabel 大小時算一遍勿锅、UILabel 繪制時內(nèi)部再算一遍)帕膜;CoreText 對象占用內(nèi)存較少,可以緩存下來以備稍后多次渲染粱甫。

4.4圖像繪制

圖像的繪制通常是指用那些以 CG 開頭的方法把圖像繪制到畫布中泳叠,然后從畫布創(chuàng)建圖片并顯示這樣一個過程。這個過程我們可以用異步繪制的思想解決這個問題茶宵,發(fā)揮cpu多核的優(yōu)勢。

五宗挥、GPU相關(guān)優(yōu)化

相對于 CPU 來說乌庶,GPU 能干的事情比較單一:接收提交的紋理(Texture)和頂點描述(三角形)种蝶,應(yīng)用變換(transform)、混合并渲染瞒大,然后輸出到屏幕上螃征。
GPU處理的單位是Texture,基本上我們控制GPU都是通過OpenGL來完成的,但是從bitmap到Texture之間需要一座橋梁透敌,Core Animation正好充當(dāng)了這個角色:
Core Animation對OpenGL的api有一層封裝盯滚,當(dāng)我們的要渲染的layer已經(jīng)有了bitmap content的時候,這個content一般來說是一個CGImageRef酗电,CoreAnimation會創(chuàng)建一個OpenGL的Texture并將CGImageRef(bitmap)和這個Texture綁定魄藕,通過TextureID來標(biāo)識。
這個對應(yīng)關(guān)系建立起來之后撵术,剩下的任務(wù)就是GPU如何將Texture渲染到屏幕上了背率。

5.1視圖混合(Composing)

當(dāng)多個視圖(或者說 CALayer)重疊在一起顯示時,GPU 會首先把他們混合到一起嫩与。如果視圖結(jié)構(gòu)過于復(fù)雜寝姿,混合的過程也會消耗很多 GPU 資源。為了減輕這種情況的 GPU 消耗划滋,應(yīng)用應(yīng)當(dāng)盡量減少視圖數(shù)量和層次饵筑,并在不透明的視圖里標(biāo)明 opaque 屬性以避免無用的 Alpha 通道合成。當(dāng)然处坪,這也可以用上面的方法翻翩,把多個視圖預(yù)先渲染為一張圖片來顯示。

5.2圖形的生成

CALayer 的 border稻薇、圓角嫂冻、陰影、遮罩(mask)塞椎,CASharpLayer 的矢量圖形顯示桨仿,通常會觸發(fā)離屏渲染(offscreen rendering),而離屏渲染通常發(fā)生在 GPU 中案狠。當(dāng)一個列表視圖中出現(xiàn)大量圓角的 CALayer服傍,并且快速滑動時,可以觀察到 GPU 資源已經(jīng)占滿骂铁,而 CPU 資源消耗很少吹零。這時界面仍然能正常滑動拉庵,但平均幀數(shù)會降到很低灿椅。為了避免這種情況,可以嘗試開啟 CALayer.shouldRasterize 屬性,但這會把原本離屏渲染的操作轉(zhuǎn)嫁到 CPU 上去茫蛹。對于只需要圓角的某些場合操刀,也可以用一張已經(jīng)繪制好的圓角圖片覆蓋到原本視圖上面來模擬相同的視覺效果。最徹底的解決辦法婴洼,就是把需要顯示的圖形在后臺線程繪制為圖片骨坑,避免使用圓角、陰影柬采、遮罩等屬性欢唾。

5.3紋理的渲染

所有的 Bitmap,包括圖片粉捻、文本礁遣、柵格化的內(nèi)容,最終都要由內(nèi)存提交到顯存杀迹,綁定為 GPU Texture亡脸。不論是提交到顯存的過程,還是 GPU 調(diào)整和渲染 Texture 的過程树酪,都要消耗不少 GPU 資源浅碾。當(dāng)在較短時間顯示大量圖片時(比如 TableView 存在非常多的圖片并且快速滑動時),CPU 占用率很低续语,GPU 占用非常高垂谢,界面仍然會掉幀。避免這種情況的方法只能是盡量減少在短時間內(nèi)大量圖片的顯示疮茄,盡可能將多張圖片合成為一張進行顯示滥朱。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市力试,隨后出現(xiàn)的幾起案子徙邻,更是在濱河造成了極大的恐慌,老刑警劉巖畸裳,帶你破解...
    沈念sama閱讀 217,406評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件缰犁,死亡現(xiàn)場離奇詭異,居然都是意外死亡怖糊,警方通過查閱死者的電腦和手機帅容,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,732評論 3 393
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來伍伤,“玉大人并徘,你說我怎么就攤上這事∪呕辏” “怎么了麦乞?”我有些...
    開封第一講書人閱讀 163,711評論 0 353
  • 文/不壞的土叔 我叫張陵蕴茴,是天一觀的道長。 經(jīng)常有香客問我路幸,道長荐开,這世上最難降的妖魔是什么付翁? 我笑而不...
    開封第一講書人閱讀 58,380評論 1 293
  • 正文 為了忘掉前任简肴,我火速辦了婚禮,結(jié)果婚禮上百侧,老公的妹妹穿的比我還像新娘砰识。我一直安慰自己,他們只是感情好佣渴,可當(dāng)我...
    茶點故事閱讀 67,432評論 6 392
  • 文/花漫 我一把揭開白布辫狼。 她就那樣靜靜地躺著,像睡著了一般辛润。 火紅的嫁衣襯著肌膚如雪膨处。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,301評論 1 301
  • 那天砂竖,我揣著相機與錄音真椿,去河邊找鬼。 笑死乎澄,一個胖子當(dāng)著我的面吹牛突硝,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播置济,決...
    沈念sama閱讀 40,145評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼解恰,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了浙于?” 一聲冷哼從身側(cè)響起护盈,我...
    開封第一講書人閱讀 39,008評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎羞酗,沒想到半個月后腐宋,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,443評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡整慎,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,649評論 3 334
  • 正文 我和宋清朗相戀三年脏款,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片裤园。...
    茶點故事閱讀 39,795評論 1 347
  • 序言:一個原本活蹦亂跳的男人離奇死亡撤师,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出拧揽,到底是詐尸還是另有隱情剃盾,我是刑警寧澤腺占,帶...
    沈念sama閱讀 35,501評論 5 345
  • 正文 年R本政府宣布,位于F島的核電站痒谴,受9級特大地震影響衰伯,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜积蔚,卻給世界環(huán)境...
    茶點故事閱讀 41,119評論 3 328
  • 文/蒙蒙 一意鲸、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧尽爆,春花似錦怎顾、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,731評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至幅狮,卻和暖如春募强,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背崇摄。 一陣腳步聲響...
    開封第一講書人閱讀 32,865評論 1 269
  • 我被黑心中介騙來泰國打工擎值, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人配猫。 一個月前我還...
    沈念sama閱讀 47,899評論 2 370
  • 正文 我出身青樓幅恋,卻偏偏與公主長得像,于是被迫代替她去往敵國和親泵肄。 傳聞我的和親對象是個殘疾皇子捆交,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,724評論 2 354

推薦閱讀更多精彩內(nèi)容