前言
本文閱讀建議
1.一定要辯證的看待本文.
2.本文所表達(dá)觀點(diǎn)并不是最終觀點(diǎn),還會(huì)更新,因?yàn)楸救诉€在學(xué)習(xí)過(guò)程中,有什么遺漏或錯(cuò)誤還望各位指出.
3.覺(jué)得哪里不妥請(qǐng)?jiān)谠u(píng)論留下建議~
4.覺(jué)得還行的話就點(diǎn)個(gè)小心心鼓勵(lì)下我吧~
在最近的面試中,我發(fā)現(xiàn)一道面試題,其考點(diǎn)是:圍繞iOS App中一個(gè)視圖從添加到完全渲染,在這個(gè)過(guò)程中,iOS系統(tǒng)都做了什么?
在進(jìn)行了大量的文章查閱以及學(xué)習(xí)以后,將所有較為可靠的資料總結(jié)一下供大家參考。
面試題
本文可為以下面試題提供參考:
- app從點(diǎn)擊屏幕(硬件)到完全渲染,中間發(fā)生了什么?越詳細(xì)越好 要求講到進(jìn)程間通信?出處
- 一個(gè)UIImageView添加到視圖上以后,內(nèi)部是如何渲染到手機(jī)上的,請(qǐng)簡(jiǎn)述其流程?
- 在一個(gè)表內(nèi)有很多cell,每個(gè)cell上有很多個(gè)視圖,如何解決卡頓問(wèn)題?
- UIView與CALayer的區(qū)別?
簡(jiǎn)答
iOS渲染視圖的核心是Core Animation
其渲染層次依次為:圖層樹(shù)->呈現(xiàn)樹(shù)->渲染樹(shù)
- CPU階段
- 布局(Frame)
- 顯示(Core Graphics)
- 準(zhǔn)備(QuartzCore/Core Animation)
- 通過(guò)IPC提交(打包好的圖層樹(shù)以及動(dòng)畫(huà)屬性)
- OpenGL ES階段
- 生成(Generate)
- 綁定(Bind)
- 緩存數(shù)據(jù)(Buffer Data)
- 啟用(Enable)
- 設(shè)置指針(Set Pointers)
- 繪圖(Draw)
- 清除(Delete)
- GPU階段
- 接收提交的紋理(Texture)和頂點(diǎn)描述(三角形)
- 應(yīng)用變換(transform)
- 合并渲染(離屏渲染等)
其iOS平臺(tái)渲染核心原理的重點(diǎn)主要圍繞前后幀緩存、Vsync信號(hào)、CADisplayLink
文字簡(jiǎn)答:
- 首先一個(gè)視圖由CPU進(jìn)行Frame布局,準(zhǔn)備視圖和圖層的層級(jí)關(guān)系,查詢是否有重寫(xiě)
drawRect:或drawLayer:inContext:方法,注意:如果有重寫(xiě)的話,這里的渲染是會(huì)占用CPU進(jìn)行處理的。 - CPU會(huì)將處理視圖和圖層的層級(jí)關(guān)系打包,通過(guò)IPC(內(nèi)部處理通信)通道提交給渲染服務(wù),渲染服務(wù)由OpenGL ES和GPU組成。
- 渲染服務(wù)首先將圖層數(shù)據(jù)交給OpenGL ES進(jìn)行紋理生成和著色。生成前后幀緩存,再根據(jù)顯示硬件的刷新頻率,一般以設(shè)備的
VSync信號(hào)和CADisplayLink為標(biāo)準(zhǔn),進(jìn)行前后幀緩存的切換。 - 最后,將最終要顯示在畫(huà)面上的后幀緩存交給GPU,進(jìn)行采集圖片和形狀,運(yùn)行變換,應(yīng)用紋理和混合。最終顯示在屏幕上。
以上僅僅是對(duì)該題簡(jiǎn)單回答,其中的原理以及瓶頸和優(yōu)化,后面會(huì)詳細(xì)介紹。
知識(shí)點(diǎn)
- 重新認(rèn)識(shí)Core Animation
- CPU渲染職能
- OpenGL ES渲染職能
- GPU渲染職能
- IPC內(nèi)部通信(進(jìn)程間通信)
- 前后幀緩存&Vsync信號(hào)
- 視圖渲染優(yōu)化&卡頓優(yōu)化
- Metal渲染引擎
- 事件響應(yīng)鏈&Runloop原理
- CALayer的職能
重新認(rèn)識(shí)Core Animation
蘋(píng)果官方文檔-Core Animation
Core Animation并僅僅是字面意思的核心動(dòng)畫(huà),而是整個(gè)顯示核心都是圍繞QuartzCore框架中的Core Animation

Core Animation是依賴于OpenGL ES做GPU渲染,CoreGraphics做CPU渲染,但在本文中,以及官方文檔都是將OpenGL與GPU分開(kāi)說(shuō)明。

Core Animation 在 RunLoop 中注冊(cè)了一個(gè) Observer,監(jiān)聽(tīng)了 BeforeWaiting 和 Exit 事件。這個(gè) Observer 的優(yōu)先級(jí)是 2000000,低于常見(jiàn)的其他 Observer。當(dāng)一個(gè)觸摸事件到來(lái)時(shí),RunLoop 被喚醒,App 中的代碼會(huì)執(zhí)行一些操作,比如創(chuàng)建和調(diào)整視圖層級(jí)、設(shè)置 UIView 的 frame、修改 CALayer 的透明度、為視圖添加一個(gè)動(dòng)畫(huà);這些操作最終都會(huì)被 CALayer 捕獲,并通過(guò) CATransaction 提交到一個(gè)中間狀態(tài)去(CATransaction 的文檔略有提到這些內(nèi)容,但并不完整)。當(dāng)上面所有操作結(jié)束后,RunLoop 即將進(jìn)入休眠(或者退出)時(shí),關(guān)注該事件的 Observer 都會(huì)得到通知。這時(shí) CA 注冊(cè)的那個(gè) Observer 就會(huì)在回調(diào)中,把所有的中間狀態(tài)合并提交到 GPU 去顯示;如果此處有動(dòng)畫(huà),CA 會(huì)通過(guò) DisplayLink 等機(jī)制多次觸發(fā)相關(guān)流程。
CPU渲染職能
在這里推薦大家去閱讀落影l(fā)oyinglin的文章iOS開(kāi)發(fā)-視圖渲染與性能優(yōu)化
- 顯示邏輯
- CoreAnimation提交會(huì)話,包括自己和子樹(shù)(view hierarchy)的layout狀態(tài)等;
- RenderServer解析提交的子樹(shù)狀態(tài),生成繪制指令
- GPU執(zhí)行繪制指令
- 顯示渲染后的數(shù)據(jù)

- 提交流程
- 布局(Layout)
- 調(diào)用layoutSubviews方法
- 調(diào)用addSubview:方法
- 顯示(Display)
- 通過(guò)drawRect繪制視圖;
- 繪制string(字符串);
- 準(zhǔn)備提交(Prepare)
- 解碼圖片;
- 圖片格式轉(zhuǎn)換;
- 提交(Commit)
- 打包layers并發(fā)送到渲染server;
- 遞歸提交子樹(shù)的layers;
- 如果子樹(shù)太復(fù)雜,會(huì)消耗很大,對(duì)性能造成影響;
- 布局(Layout)
CPU渲染職能主要體現(xiàn)在以下5個(gè)方面:
布局計(jì)算
如果你的視圖層級(jí)過(guò)于復(fù)雜,當(dāng)視圖呈現(xiàn)或者修改的時(shí)候,計(jì)算圖層幀率就會(huì)消耗一部分時(shí)間。特別是使用iOS6的自動(dòng)布局機(jī)制尤為明顯,它應(yīng)該是比老版的自動(dòng)調(diào)整邏輯加強(qiáng)了CPU的工作。
視圖懶加載
iOS只會(huì)當(dāng)視圖控制器的視圖顯示到屏幕上時(shí)才會(huì)加載它。這對(duì)內(nèi)存使用和程序啟動(dòng)時(shí)間很有好處,但是當(dāng)呈現(xiàn)到屏幕上之前,按下按鈕導(dǎo)致的許多工作都會(huì)不能被及時(shí)響應(yīng)。比如控制器從數(shù)據(jù)庫(kù)中獲取數(shù)據(jù),或者視圖 從一個(gè)nib文件中加載,或者涉及IO的圖片顯示,都會(huì)比CPU正常操作慢得多。
Core Graphics繪制
如果對(duì)視圖實(shí)現(xiàn)了drawRect:或drawLayer:inContext:方法,或者 CALayerDelegate 的 方法,那么在繪制任何東 西之前都會(huì)產(chǎn)生一個(gè)巨大的性能開(kāi)銷。為了支持對(duì)圖層內(nèi)容的任意繪制,Core Animation必須創(chuàng)建一個(gè)內(nèi)存中等大小的寄宿圖片。然后一旦繪制結(jié)束之后, 必須把圖片數(shù)據(jù)通過(guò)IPC傳到渲染服務(wù)器。在此基礎(chǔ)上,Core Graphics繪制就會(huì)變得十分緩慢,所以在一個(gè)對(duì)性能十分挑剔的場(chǎng)景下這樣做十分不好。
解壓圖片
PNG或者JPEG壓縮之后的圖片文件會(huì)比同質(zhì)量的位圖小得多。但是在圖片繪制到屏幕上之前,必須把它擴(kuò)展成完整的未解壓的尺寸(通常等同于圖片寬 x 長(zhǎng) x 4個(gè)字節(jié))。為了節(jié)省內(nèi)存,iOS通常直到真正繪制的時(shí)候才去解碼圖片。根據(jù)你加載圖片的方式,第一次對(duì) 圖層內(nèi)容賦值的時(shí)候(直接或者間接使用 UIImageView )或者把它繪制到 Core Graphics中,都需要對(duì)它解壓,這樣的話,對(duì)于一個(gè)較大的圖片,都會(huì)占用一定的時(shí)間。
圖層打包
當(dāng)圖層被成功打包,發(fā)送到渲染服務(wù)器之后,CPU仍然要做如下工作:為了顯示 屏幕上的圖層,Core Animation必須對(duì)渲染樹(shù)種的每個(gè)可見(jiàn)圖層通過(guò)OpenGL循環(huán) 轉(zhuǎn)換成紋理三角板。由于GPU并不知曉Core Animation圖層的任何結(jié)構(gòu),所以必須 要由CPU做這些事情。這里CPU涉及的工作和圖層個(gè)數(shù)成正比,所以如果在你的層 級(jí)關(guān)系中有太多的圖層,就會(huì)導(dǎo)致CPU沒(méi)一幀的渲染,即使這些事情不是你的應(yīng)用 程序可控的。
OpenGL ES渲染職能
這里推薦大家去看《OpenGL ES應(yīng)用開(kāi)發(fā)實(shí)踐指南:iOS卷》,因?yàn)槠^(guò)長(zhǎng),就不贅述OpenGL的原理。

簡(jiǎn)單來(lái)說(shuō),OpenGL ES是對(duì)圖層進(jìn)行取色,采樣,生成紋理,綁定數(shù)據(jù),生成前后幀緩存。
紋理的概念:紋理是一個(gè)用來(lái)保存圖像的顏色元??值的 OpenGL ES 緩存,可以簡(jiǎn)單理解為一個(gè)單位。
1)生成(Generate)— 請(qǐng) OpenGL ES 為圖形處理器制的緩存生成一個(gè)獨(dú)一無(wú)二的標(biāo)識(shí)符。
2)綁定(Bind)— 告訴 OpenGL ES 為接下來(lái)的運(yùn)算使用一個(gè)緩存。
3)緩存數(shù)據(jù)(Buffer Data)— 讓 OpenGL ES 為當(dāng)前定的緩存分配并初始化 夠的內(nèi)存(通常是從 CPU 制的內(nèi)存復(fù)制數(shù)據(jù)到分配的內(nèi)存)。
4)啟用(Enable)或者(Disable)— 告訴 OpenGL ES 在接下來(lái)的渲染中是 使用緩存中的數(shù)據(jù)。
5)設(shè)置指(Set Pointers)— 告訴 Open-GL ES 在緩存中的數(shù)據(jù)的類型和所有需 要的數(shù)據(jù)的內(nèi)存移值。
6)繪圖(Draw) — 告訴 OpenGL ES 使用當(dāng)前定并啟用的緩存中的數(shù)據(jù)渲染 整個(gè)場(chǎng)景或者某個(gè)場(chǎng)景的一部分。
7)刪除除(Delete)— 告訴 OpenGL ES 除以前生成的緩存并釋相關(guān)的資源。
當(dāng)顯示一個(gè)UIImageView時(shí),Core Animation會(huì)創(chuàng)建一個(gè)OpenGL ES紋理,并確保在這個(gè)圖層中的位圖被上傳到對(duì)應(yīng)的紋理中。當(dāng)你重寫(xiě)-drawInContext方法時(shí),Core Animation會(huì)請(qǐng)求分配一個(gè)紋理,同時(shí)確保Core Graphics會(huì)將你在-drawInContext中繪制的東西放入到紋理的位圖數(shù)據(jù)中。

iOS 操作系統(tǒng)不會(huì)讓?xiě)?yīng)用直接向前幀緩存或者 后幀緩存繪圖,也不會(huì)讓?xiě)?yīng)用直接復(fù)制前幀緩存和后幀緩存之間的切換。操作系統(tǒng)為自 己保留了這些操作,以便它可以隨時(shí)使用 Core Animation 合成器來(lái)控制顯示的最終外觀
最終,生成前后幀緩存會(huì)再交由GPU進(jìn)行最后一步的工作。
GPU渲染職能
GPU會(huì)根據(jù)生成的前后幀緩存數(shù)據(jù),根據(jù)實(shí)際情況進(jìn)行合成,其中造成GPU渲染負(fù)擔(dān)的一般是:離屏渲染,圖層混合,延遲加載。

- 普通的Tile-Based渲染流程
- CommandBuffer,接受OpenGL ES處理完畢的渲染指令;
- Tiler,調(diào)用頂點(diǎn)著色器,把頂點(diǎn)數(shù)據(jù)進(jìn)行分塊(Tiling);
- ParameterBuffer,接受分塊完畢的tile和對(duì)應(yīng)的渲染參數(shù);
- Renderer,調(diào)用片元著色器,進(jìn)行像素渲染;
-RenderBuffer,存儲(chǔ)渲染完畢的像素;
- 離屏渲染 —— 遮罩(Mask)
- 渲染layer的mask紋理,同Tile-Based的基本渲染邏輯;
- 渲染layer的content紋理,同Tile-Based的基本渲染邏輯;
- Compositing操作,合并1、2的紋理;
- 離屏渲染 ——UIVisiualEffectView
- 渲染等待
- 光柵化
- 組透明度
GPU用來(lái)采集圖片和形狀,運(yùn)行變換,應(yīng)用文理和混合,最終把它們輸送到屏幕上。
太多的幾何結(jié)構(gòu)會(huì)影響GPU速度,但這并不是GPU的瓶頸限制原因,但由于圖層在顯示之前要通過(guò)IPC發(fā)送到渲染服務(wù)器的時(shí)候(圖層實(shí)際上是由很多小物體組成的特別重量級(jí)的對(duì)象),太多的圖層就會(huì)引起CPU的瓶頸。
重繪。主要由重疊的半透明圖層引起。GPU的填充比率(用顏色填充像素的比率)是有限的,所以要避免重繪。
IPC內(nèi)部通信(進(jìn)程間通信)
在研究這個(gè)問(wèn)題的過(guò)程中,我有想過(guò)去看一下源碼,試著去理解在視圖完全渲染之前,IPC是如何調(diào)度的,可惜蘋(píng)果并沒(méi)有開(kāi)源繪制過(guò)程中的代碼。這里推薦官方文章給大家了解一下iOS中IPC是如何運(yùn)作的。
蘋(píng)果官方文檔-Mach內(nèi)核編程 IPC通信
前后幀緩存&Vsync信號(hào)
雖然我們不能看到蘋(píng)果內(nèi)部是如何實(shí)現(xiàn)的,但是蘋(píng)果官方也提供了我們可以參考的對(duì)象,也就是VSync信號(hào)和CADisplayLink對(duì)象。
iOS 的顯示系統(tǒng)是由 VSync 信號(hào)驅(qū)動(dòng)的,VSync 信號(hào)由硬件時(shí)鐘生成,每秒鐘發(fā)出 60 次(這個(gè)值取決設(shè)備硬件,比如 iPhone 真機(jī)上通常是 59.97)。iOS 圖形服務(wù)接收到 VSync 信號(hào)后,會(huì)通過(guò) IPC 通知到 App 內(nèi)。App 的 Runloop 在啟動(dòng)后會(huì)注冊(cè)對(duì)應(yīng)的 CFRunLoopSource 通過(guò) mach_port 接收傳過(guò)來(lái)的時(shí)鐘信號(hào)通知,隨后 Source 的回調(diào)會(huì)驅(qū)動(dòng)整個(gè) App 的動(dòng)畫(huà)與顯示。

幀緩存:接收渲染結(jié)果的緩沖區(qū),為GPU指定存儲(chǔ)渲染結(jié)果的區(qū)域
幀緩存可以同時(shí)存在多個(gè),但是屏幕顯示像素受到保存在前幀緩存(front frame buffer)的特定幀緩存中的像素顏色元素的控制。
程序的渲染結(jié)果通常保存在后幀緩存(back frame buffer)在內(nèi)的其他幀緩存,當(dāng)渲染后的后幀緩存完成后,前后幀緩存會(huì)互換。(這部分操作由操作系統(tǒng)來(lái)完成)
前幀緩存決定了屏幕上顯示的像素顏色,會(huì)在適當(dāng)?shù)臅r(shí)候與后幀緩存切換。
Core Animation的合成器會(huì)聯(lián)合OpenGL ES層和UIView層、StatusBar層等,在后幀緩存混合產(chǎn)生最終的顏色,并切換前后幀緩存;
OpenGL ES坐標(biāo)是以浮點(diǎn)數(shù)來(lái)存儲(chǔ),即使是其他數(shù)據(jù)類型的頂點(diǎn)數(shù)據(jù)也會(huì)被轉(zhuǎn)化成浮點(diǎn)型;
視圖加載
那么在了解iOS視圖渲染流程以后,再來(lái)看一下第二題:
一個(gè)UIImageView添加到視圖上以后,內(nèi)部是如何渲染到手機(jī)上的,請(qǐng)簡(jiǎn)述其流程?
圖片的顯示分為三步:加載、解碼、渲染。
通常,我們操作的只有加載,解碼和渲染是由UIKit進(jìn)行。
以UIImageView為例。當(dāng)其顯示在屏幕上時(shí),需要UIImage作為數(shù)據(jù)源。
UIImage持有的數(shù)據(jù)是未解碼的壓縮數(shù)據(jù),能節(jié)省較多的內(nèi)存和加快存儲(chǔ)。
當(dāng)UIImage被賦值給UIImage時(shí)(例如imageView.image = image;),圖像數(shù)據(jù)會(huì)被解碼,變成RGB的顏色數(shù)據(jù)。
解碼是一個(gè)計(jì)算量較大的任務(wù),且需要CPU來(lái)執(zhí)行;并且解碼出來(lái)的圖片體積與圖片的寬高有關(guān)系,而與圖片原來(lái)的體積無(wú)關(guān)。
此處引用-->iOS性能優(yōu)化——圖片加載和處理
我查看了較為流行的第三方庫(kù)源碼,例如YYImage、SDWebImage、FastImageCache,其中加載一個(gè)圖片的流程大致為:
- 查看UIImageView的API我們可以發(fā)現(xiàn),UIImage封裝了一個(gè)
CoreGraphics/CGImage的對(duì)象。
1.+[UIImage imageWithContentsOfFile:]使用Image I/O創(chuàng)建CGImageRef內(nèi)存映射數(shù)據(jù)。此時(shí),圖像尚未解碼。 - 返回的圖像被分配給UIImageView。
- 如果圖像數(shù)據(jù)為未解碼的PNG/JPG,解碼為位圖數(shù)據(jù)
- 隱式CATransaction捕獲到UIImageView layer樹(shù)的變化
- 在主運(yùn)行循環(huán)的下一次迭代中,Core Animation提交隱式事務(wù),這會(huì)涉及創(chuàng)建已設(shè)置為層內(nèi)容的所有圖像的副本,根據(jù)圖像:
- 緩沖區(qū)被分配用于管理文件IO和解壓縮操作。
- 文件數(shù)據(jù)從磁盤(pán)讀入內(nèi)存。
- 壓縮的圖像數(shù)據(jù)被解碼成其未壓縮的位圖形式
- Core Animation使用未壓縮的位圖數(shù)據(jù)來(lái)渲染圖層。
再看一下YYImage的源碼,其流程也大致為:
- 獲取圖片二進(jìn)制數(shù)據(jù)
- 創(chuàng)建一個(gè)CGImageRef對(duì)象
- 使用
CGBitmapContextCreate()方法創(chuàng)建一個(gè)上下文對(duì)象 - 使用
CGContextDrawImage()方法繪制到上下文 - 使用
CGBitmapContextCreateImage()生成CGImageRef對(duì)象。 - 最后使用
imageWithCGImage()方法將CGImage轉(zhuǎn)化為UIImage。
當(dāng)然YYImage不止做了這些,還有解碼器編碼器,支持webP等多種格式,并且還寫(xiě)了自定義的操作隊(duì)列,對(duì)網(wǎng)絡(luò)加載圖片進(jìn)行了優(yōu)化。在此不贅述。
推薦文章:
蘋(píng)果官方文檔-CGImage位圖
iOS圖片加載速度極限優(yōu)化—FastImageCache解析
Image I/O詳解的文章
在這里同時(shí)推薦Y大的兩篇文章
移動(dòng)端圖片格式調(diào)研
iOS 處理圖片的一些小 Tip
視圖渲染優(yōu)化&卡頓優(yōu)化
接下來(lái)我們看一下第三題:在一個(gè)表內(nèi)有很多cell,每個(gè)cell上有很多個(gè)視圖,如何解決卡頓問(wèn)題?
什么是卡頓?蘋(píng)果官方文章-顯示幀率

當(dāng)你的主線程操作卡頓超過(guò)16.67ms以后,你的應(yīng)用就會(huì)出現(xiàn)掉幀,丟幀的情況。也就是卡頓。
一般來(lái)說(shuō)造成卡頓的原因,就是CPU負(fù)擔(dān)過(guò)重,響應(yīng)時(shí)間過(guò)長(zhǎng)。主要原因有以下幾種:
- 隱式繪制 CGContext
- 文本CATextLayer 和 UILabel
- 光柵化 shouldRasterize
- 離屏渲染
- 可伸縮圖片
- shadowPath
- 混合和過(guò)度繪制
- 減少圖層數(shù)量
- 裁切
- 對(duì)象回收
- Core Graphics繪制
- -renderInContext: 方法
其中最常見(jiàn)的問(wèn)題就是離屏渲染:
離屏渲染:離屏繪制發(fā)生在基于CPU或者是GPU的渲染,或者是為離屏圖 片分配額外內(nèi)存,以及切換繪制上下文,這些都會(huì)降低GPU性能。對(duì)于特定圖 層效果的使用,比如圓角,圖層遮罩,陰影或者是圖層光柵化都會(huì)強(qiáng)制Core Animation提前渲染圖層的離屏繪制。
如果視圖繪制超出GPU支持的2048x2048或者4096x4096尺寸的 紋理,就必須要用CPU在圖層每次顯示之前對(duì)圖片預(yù)處理,同樣也會(huì)降低性能。
那么如何在需要渲染大量視圖的情況下,還能保證流暢度,也就是保證FPS。
在這里推薦閱讀郭曜源前輩的iOS 保持界面流暢的技巧
以及indulge_in的YYAsyncLayer剖析
我參考了YYAsyncLayer,他其中的原理大致是這樣的:
YYAsyncLayer原理
YYAsyncLayer 是 CALayer 的子類,當(dāng)它需要顯示內(nèi)容(比如調(diào)用了 [layer setNeedDisplay])時(shí),它會(huì)向 delegate,也就是 UIView 請(qǐng)求一個(gè)異步繪制的任務(wù)。在異步繪制時(shí),Layer 會(huì)傳遞一個(gè) BOOL(^isCancelled)() 這樣的 block,繪制代碼可以隨時(shí)調(diào)用該 block 判斷繪制任務(wù)是否已經(jīng)被取消。
當(dāng) TableView 快速滑動(dòng)時(shí),會(huì)有大量異步繪制任務(wù)提交到后臺(tái)線程去執(zhí)行。但是有時(shí)滑動(dòng)速度過(guò)快時(shí),繪制任務(wù)還沒(méi)有完成就可能已經(jīng)被取消了。如果這時(shí)仍然繼續(xù)繪制,就會(huì)造成大量的 CPU 資源浪費(fèi),甚至阻塞線程并造成后續(xù)的繪制任務(wù)遲遲無(wú)法完成。我的做法是盡量快速、提前判斷當(dāng)前繪制任務(wù)是否已經(jīng)被取消;在繪制每一行文本前,我都會(huì)調(diào)用 isCancelled() 來(lái)進(jìn)行判斷,保證被取消的任務(wù)能及時(shí)退出,不至于影響后續(xù)操作。
AsyncDisplayKit原理
ASDK 在此處模擬了 Core Animation 的這個(gè)機(jī)制:所有針對(duì) ASNode 的修改和提交,總有些任務(wù)是必需放入主線程執(zhí)行的。當(dāng)出現(xiàn)這種任務(wù)時(shí),ASNode 會(huì)把任務(wù)用 ASAsyncTransaction(Group) 封裝并提交到一個(gè)全局的容器去。ASDK 也在 RunLoop 中注冊(cè)了一個(gè) Observer,監(jiān)視的事件和 CA 一樣,但優(yōu)先級(jí)比 CA 要低。當(dāng) RunLoop 進(jìn)入休眠前、CA 處理完事件后,ASDK 就會(huì)執(zhí)行該 loop 內(nèi)提交的所有任務(wù)。
Tips
優(yōu)化方案圍繞著 使用多線程調(diào)用,合理利用CPU計(jì)算位置,布局,層次,解壓等,再合理調(diào)度GPU進(jìn)行渲染,GPU負(fù)擔(dān)常常要比CPU大,合理調(diào)度CPU進(jìn)行計(jì)算可以減輕GPU渲染負(fù)擔(dān),使應(yīng)用更加流暢。
Metal渲染引擎
當(dāng)你現(xiàn)在再去查閱官方文檔時(shí),你會(huì)發(fā)現(xiàn)蘋(píng)果官方已經(jīng)使用Metal去替代OpenGL ES作為Core Animation的渲染。
看一下蘋(píng)果官方文檔-Metal可以發(fā)現(xiàn),早在

蘋(píng)果將Metal作為新的渲染引擎,更好的利用了GPU的性能,同時(shí)保證了低內(nèi)存占用和省電,但我個(gè)人并沒(méi)有深入研究Metal,這里可以有興趣的同學(xué)可以看一下落影前輩的文章:
Metal入門教程總結(jié)
Metal入門教程(八)Metal與OpenGL ES交互
OpenGL 專題
事件響應(yīng)鏈&原理
最后一題:UIView和CALayer的區(qū)別?
如果你已經(jīng)做了幾年iOS開(kāi)發(fā),相比對(duì)于這道題可能已經(jīng)很熟悉。
最直接的回答就是UIView可以響應(yīng)用戶事件,而CALayer不能處理事件
首先要講一下App中的事件響應(yīng)鏈,它分為兩部分:Hit-Testing事件傳遞 & Runloop原理
當(dāng)用戶對(duì)屏幕進(jìn)行了操作,產(chǎn)生了一個(gè)用戶事件。
蘋(píng)果注冊(cè)了一個(gè) Source1 (基于 mach port 的) 用來(lái)接收系統(tǒng)事件,其回調(diào)函數(shù)為
__IOHIDEventSystemClientQueueCallback()當(dāng)一個(gè)硬件事件(觸摸/鎖屏/搖晃等)發(fā)生后,首先由 IOKit.framework 生成一個(gè) IOHIDEvent 事件并由 SpringBoard 接收。這個(gè)過(guò)程的詳細(xì)情況可以參考這里。SpringBoard 只接收按鍵(鎖屏/靜音等),觸摸,加速,接近傳感器等幾種 Event,隨后用 mach port 轉(zhuǎn)發(fā)給需要的App進(jìn)程。隨后蘋(píng)果注冊(cè)的那個(gè) Source1 就會(huì)觸發(fā)回調(diào),并調(diào)用
_UIApplicationHandleEventQueue()進(jìn)行應(yīng)用內(nèi)部的分發(fā)。
_UIApplicationHandleEventQueue()會(huì)把 IOHIDEvent 處理并包裝成 UIEvent 進(jìn)行處理或分發(fā),其中包括識(shí)別 UIGesture/處理屏幕旋轉(zhuǎn)/發(fā)送給 UIWindow 等。通常事件比如 UIButton 點(diǎn)擊、touchesBegin/Move/End/Cancel事件都是在這個(gè)回調(diào)中完成的。
此處引用-->深入理解Runloop-事件響應(yīng)

當(dāng)前前臺(tái)運(yùn)行中應(yīng)用接收到UIEvent以后,當(dāng)用戶對(duì)屏幕進(jìn)行了操作,系統(tǒng)先循環(huán)調(diào)用Hit-test遍歷視圖棧里的視圖,順序?yàn)橐晥D層次的逆順序,用Responder Chain響應(yīng)鏈傳遞一層層給根視圖AppDelegate處理。-->蘋(píng)果官方文檔-使用響應(yīng)者和響應(yīng)者鏈來(lái)處理事件

推薦兩篇文章:
iOS 事件處理機(jī)制與圖像渲染過(guò)程
iOS事件響應(yīng)鏈中Hit-Test View的應(yīng)用
CALayer的職能
CALayer 并不清楚具體的響應(yīng)鏈,所以不能直接處理觸摸事件或者手勢(shì)。但是它提供了-containsPoint:和-hitTest:來(lái)判斷是否一個(gè)觸點(diǎn)在圖層的范圍之內(nèi)。
與UIView不同,CALayer著重于圖層的繪制,大致為以下職能:
- 陰影、圓角、邊框、蒙版、拉伸、transform、動(dòng)畫(huà)。
- 寄宿圖:你可以給CALayer.contents傳遞一個(gè)CGImage來(lái)進(jìn)行渲染,也可以調(diào)用
- (void)drawLayer:(CALayer *)layer inContext:(CGContextRef)ctx;方法進(jìn)行繪制。但通常我們會(huì)使用UIView的drawRect方法 - CATextLayer:直接將字符串使用Core Graphics寫(xiě)入圖層
- CATransformLayer:能夠用于構(gòu)造一個(gè)層級(jí)的3D結(jié)構(gòu)
CALayer 內(nèi)部并沒(méi)有屬性,當(dāng)調(diào)用屬性方法時(shí),它內(nèi)部是通過(guò)運(yùn)行時(shí)
resolveInstanceMethod為對(duì)象臨時(shí)添加一個(gè)方法,并把對(duì)應(yīng)屬性值保存到內(nèi)部的一個(gè) Dictionary 里,同時(shí)還會(huì)通知 delegate、創(chuàng)建動(dòng)畫(huà)等等,非常消耗資源。UIView 的關(guān)于顯示相關(guān)的屬性(比如frame/bounds/transform)等實(shí)際上都是 CALayer 屬性映射來(lái)的,所以對(duì) UIView 的這些屬性進(jìn)行調(diào)整時(shí),消耗的資源要遠(yuǎn)大于一般的屬性。對(duì)此你在應(yīng)用中,應(yīng)該盡量減少不必要的屬性修改。當(dāng)視圖層次調(diào)整時(shí),UIView、CALayer 之間會(huì)出現(xiàn)很多方法調(diào)用與通知,所以在優(yōu)化性能時(shí),應(yīng)該盡量避免調(diào)整視圖層次、添加和移除視圖。
使用圖層關(guān)聯(lián)的視圖而不是單獨(dú)使用 CALayer 的好處在于,你能在使用所
有 CALayer 底層特性的同時(shí),也可以使用 UIView 的高級(jí)API(比如自動(dòng)排版, 布局和事件處理)。做一些對(duì)性能特別挑剔的工作,比如對(duì) UIView 一些可忽略不計(jì)的操作都會(huì)引 起顯著的不同
關(guān)于UIView動(dòng)畫(huà)以及CALayer的動(dòng)畫(huà)這里推薦兩篇文章:
iOS-UIView與CALayer動(dòng)畫(huà)原理
CALayer與iOS動(dòng)畫(huà) 講解及使用
參考
本文大量借助了引用文章的文字描述,在此感謝各位作者的文章對(duì)本問(wèn)題的理解起了很大的幫助。也希望各位能去原文發(fā)表自己的看法。謝謝~
- 蘋(píng)果官方視頻-WWDC2014-Session419
- 蘋(píng)果官方文章-顯示幀率
- 落影l(fā)oyinglin的文章iOS開(kāi)發(fā)-視圖渲染與性能優(yōu)化
- 郭曜源前輩的文章:iOS 保持界面流暢的技巧
- iOS 事件處理機(jī)制與圖像渲染過(guò)程
- 推薦閱讀:《iOS和MacOS性能優(yōu)化》對(duì)于圖像I/O方面,書(shū)中最后有詳細(xì)解釋。
總結(jié)
iOS開(kāi)發(fā)要學(xué)的東西還有很多,因?yàn)闀r(shí)間的推移,每年的iOS崗位要求都在提高,導(dǎo)致我們?cè)趇OS開(kāi)發(fā)崗位的同學(xué)要學(xué)習(xí)很多知識(shí)。例如Runtime、Runloop、音視頻處理、視圖渲染等,各位一起加油吧。