UI視圖

UITableView相關(guān)

  • 重用機(jī)制 - 復(fù)用池 - 復(fù)用池原理
//通過dequeueReusableCellWithIdentifier獲取cell就能得到UITableView的重用機(jī)制的支持
cell = [tableView dequeueReusableCellWithIdentifier:identifier];
  • 多線程下修改或訪問數(shù)據(jù)源的同步解決方案

  1. 并發(fā)訪問,數(shù)據(jù)拷貝

主線程直接訪問數(shù)據(jù),并對(duì)數(shù)據(jù)操作進(jìn)行記錄(如刪除);
異步線程獲取數(shù)據(jù)源的拷貝,進(jìn)行數(shù)據(jù)操作,在返回主線程時(shí),將主線程對(duì)操作的記錄(如刪除)對(duì)再進(jìn)行一遍。
(記錄數(shù)據(jù)刪除;需要做數(shù)據(jù)源拷貝,對(duì)內(nèi)存會(huì)有一定開銷)

  1. 串行訪問(GCD 串行隊(duì)列)

將數(shù)據(jù)操作都添加到GCD的串行隊(duì)列中,由串行隊(duì)列保證數(shù)據(jù)的一致性
(由于主線程數(shù)據(jù)源操作也加入了串行隊(duì)列,如果之前有較耗時(shí)的操作時(shí),可能主線程的數(shù)據(jù)操作(如刪除)會(huì)有一定的延遲)

UI圖像顯示原理

  1. UIView與CALayer的關(guān)系

UIView 的顯示部分是由view.layer(CALayer)決定的,backgroundColor是對(duì)layer的同名屬性的封裝
UIView為其提供內(nèi)容,以及負(fù)責(zé)處理觸摸等事件,參與響應(yīng)鏈
CALayer負(fù)責(zé)顯示內(nèi)容contents
在設(shè)計(jì)原則上,體現(xiàn)了單一職責(zé)原則 ,體現(xiàn)了UIView與CALayer在職責(zé)上的分工

UILabel繪制和渲染流程

繪制和渲染的流程 (參考iOS繪制和渲染iOS渲染)
  • UI的布局(Layout)如:為視圖/圖層準(zhǔn)備層級(jí)關(guān)系,以及設(shè)置圖層屬性(位置,背景色,邊框等等)的階段。(UI布局、文本計(jì)算);
  • 顯示(Display)如:圖層的寄宿圖片被繪制的階段。繪制涉及到-drawRect:和-drawLayer:inContext:方法的調(diào)用;
  • 一些其他的準(zhǔn)備工作(Prepare)如: Image decoding, Image conversion(如果圖片類型不是GPU所支持的,需要對(duì)圖片進(jìn)行轉(zhuǎn)換)。
  • 提交GPU(Commit):Core Animation打包所有的圖層和動(dòng)畫,然后通過IPC(進(jìn)程內(nèi)通信)發(fā)送到渲染服務(wù)(render server,一個(gè)單獨(dú)管理動(dòng)畫和圖層組合的一個(gè)系統(tǒng)進(jìn)程)。這個(gè)步驟是遞歸的,所以如果layer tree如果比較復(fù)雜此步驟代價(jià)比較高

上面4個(gè)步驟發(fā)生在自己的應(yīng)用程序內(nèi)部,動(dòng)畫顯示到屏幕之前還有2個(gè)步驟的工作:

  • 對(duì)所有圖層屬性計(jì)算中間值,設(shè)置OpenGL幾何形狀來執(zhí)行渲染。
  • 在屏幕上渲染可見的三角形。

前5個(gè)階段都在軟件層面處理(通過CPU),只有最后一個(gè)階段被GPU執(zhí)行。6個(gè)階段中只有布局和顯示兩個(gè)階段是可以被我們控制的,Core Animation框架處理剩下的事務(wù)。

GPU(OpenGL)渲染管線()
  • 定點(diǎn)著色、圖元裝配、光柵化、片段著色、片段處理
  • 做完上訴操作后,將像素點(diǎn)提交到對(duì)應(yīng)的幀緩存區(qū)當(dāng)中(FrameBuffer)
  • 最后由視頻控制器在VSync信號(hào)到來之前去幀緩沖區(qū)中提取要顯示的內(nèi)容
  1. 卡頓&掉幀

如果屏幕刷新率為60,那么將意味著需要在16.7ms(1000ms/60)內(nèi)需要將下一幀需要顯示的內(nèi)容準(zhǔn)備好,由于垂直同步的機(jī)制,如果在一個(gè) HSync(VSync) 時(shí)間內(nèi),CPU 或者 GPU 沒有完成內(nèi)容提交,則那一幀就會(huì)被丟棄,等待下一次機(jī)會(huì)再顯示,而這時(shí)顯示屏?xí)A糁暗膬?nèi)容不變。這就是界面卡頓的原因。


UI卡頓、掉幀原理
UIView的繪制原理&異步繪制
UIView的繪制原理

UIView調(diào)用setNeedsDisplay方法其實(shí)是調(diào)用layer屬性的同名方法,這時(shí)layer并不會(huì)立刻調(diào)用display方法,而是要等到當(dāng)前runloop即將結(jié)束的時(shí)候調(diào)用display,進(jìn)入到繪制流程。在UIView中l(wèi)ayer.delegate就是UIView本身,UIView并沒有實(shí)現(xiàn)displayLayer:方法,所以進(jìn)入系統(tǒng)的繪制流程,我們可以通過實(shí)現(xiàn) displayLayer: 方法來進(jìn)行異步繪制。

  • 系統(tǒng)繪制流程
系統(tǒng)繪制流程

CALayer會(huì)在內(nèi)部創(chuàng)建一個(gè)backing store(CGContextRef)();
判斷l(xiāng)ayer是否有代理;
如果有代理:調(diào)用delegete的drawLayer:inContext, 然后在合適的時(shí)機(jī)回調(diào)代理, 在[UIView drawRect]中做一些繪制工作;
如果沒有代理:調(diào)用layer的drawInContext方法;
最后無論是哪個(gè)分支最后都把 backing store 的 bitmap 位圖提交到 GPU,也就是將生成的 bitmap 位圖賦值給 layer.content 屬性。

異步繪制流程

> 通過在自定義view的中實(shí)現(xiàn)CALayer的delegate方法 displayLayer: 來使用異步繪制,這時(shí)整個(gè)界面的顯示內(nèi)容都需要我們來繪制了(如UILabel中的文字也需要我們來繪制出來);
> 異步繪制過程中代理負(fù)責(zé)生成對(duì)應(yīng)的位圖(bitmap);
> 將bitmap賦值給layer.content屬性;

離屏渲染問題(iOS-離屏渲染詳解, iOS 關(guān)于離屏渲染的理解 以及解決方案)
GPU屏幕渲染有兩種方式:

(1)On-Screen Rendering (當(dāng)前屏幕渲染) :指的是GPU的渲染操作是在當(dāng)前用于顯示的屏幕緩沖區(qū)進(jìn)行。
(2)Off-Screen Rendering (離屏渲染):指的是在GPU在當(dāng)前屏幕緩沖區(qū)以外開辟一個(gè)緩沖區(qū)進(jìn)行渲染操作。
相比于當(dāng)前屏幕渲染,離屏渲染的代價(jià)是很高的,主要體現(xiàn)在兩個(gè)方面:
(1)創(chuàng)建新緩沖區(qū)(要想進(jìn)行離屏渲染,首先要?jiǎng)?chuàng)建一個(gè)新的緩沖區(qū))。
(2)上下文切換

何時(shí)會(huì)觸發(fā)
  • 設(shè)置圓角layer.cornerRadius(并且maskToBounds=YES時(shí))
  • 為圖層設(shè)置遮罩(layer.mask)
  • 為圖層設(shè)置陰影(layer.shadow *)
  • 光柵化(shouldRasterize,將圖轉(zhuǎn)化為一個(gè)個(gè)柵格組成的圖象。 光柵化特點(diǎn):每個(gè)元素對(duì)應(yīng)幀緩沖區(qū)中的一像素)
為何要避免離屏渲染
  • 離屏渲染需要?jiǎng)?chuàng)建新緩沖區(qū),并且整個(gè)過程需要多次切換上下文環(huán)境:先是從當(dāng)前屏幕(On-Screen)切換到離屏(Off-Screen),等到離屏渲染結(jié)束以后,將離屏緩沖區(qū)的渲染結(jié)果顯示到屏幕上又需要將上下文環(huán)境從離屏切換到當(dāng)前屏幕。而上下文環(huán)境的切換是要付出較大的消耗。
  • 離屏渲染發(fā)生在GPU上,觸發(fā)了OpenGL上的多通道渲染管線,產(chǎn)生了額外開銷,加大了GPU的工作量,為避免CPU和GPU工作時(shí)間超過了一個(gè)刷新時(shí)(HSync、HSync),從而造成丟幀,所以應(yīng)該盡量避免離屏渲染
UIScrollView、UITableView滑動(dòng)優(yōu)化方案
  • 減緩CPU的壓力 讓其有更多時(shí)間來處理用戶交互
    • 對(duì)象的創(chuàng)建、調(diào)整、銷毀操作放到子線程去做(可以節(jié)省一部分CPU時(shí)間);
    • 預(yù)拍版(UI的布局計(jì)算、文本計(jì)算放到子線程去做)
    • 預(yù)渲染(文本等異步繪制、圖片編解碼等)
  • 減緩GPU壓力
    • 紋理渲染(離屏渲染如圓角、maskToBounds等會(huì)加大GPU的壓力,可以使用CPU的異步繪制機(jī)制來減少離屏渲染)
    • 視圖混合(視圖層級(jí)復(fù)雜層層疊加,GPU需要做視圖混合,并且需要消耗大量的計(jì)算量來計(jì)算每一個(gè)像素值,可以通過減輕視圖層級(jí)復(fù)雜性或者通過CPU的異步繪制來讓位圖層級(jí)層級(jí)減少)

事件傳遞機(jī)制與視圖響應(yīng)鏈

  • 事件傳遞機(jī)制>
//最終那個(gè)視圖響應(yīng)事件
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event;
//判斷點(diǎn)是否在當(dāng)前視圖范圍
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event;

hitTest: withEvent:方法內(nèi)部流程


hitTest: withEvent:方法內(nèi)部實(shí)現(xiàn)
點(diǎn)擊屏幕的事件傳遞流程
事件傳遞流程
  • 視圖事件響應(yīng)
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event;
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event;
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event;

點(diǎn)擊在View C2上時(shí),如果C2不響應(yīng)事件,向上找父視圖B2,如果B2不響應(yīng),繼續(xù)向上找父視圖A,如果A不響應(yīng)事件,將繼續(xù)沿著響應(yīng)鏈傳遞如UIWindow,最后直到UIApplocation,如果最后仍然沒有視圖來處理事件,則這個(gè)事件就會(huì)被忽略


視圖事件響應(yīng)
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容