UI視圖

UITableView相關

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

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

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

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

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

UI圖像顯示原理

  1. UIView與CALayer的關系

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

UILabel繪制和渲染流程

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

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

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

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

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

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


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

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

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

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

異步繪制流程

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

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

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

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

事件傳遞機制與視圖響應鏈

  • 事件傳遞機制>
//最終那個視圖響應事件
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event;
//判斷點是否在當前視圖范圍
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event;

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


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

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


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

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

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