iOS 面試 -- UI視圖

前言

做 APP 開發(fā)與我們聯(lián)系最緊密的就是 UI 的開發(fā)了,你做 APP 開發(fā),不做 UI 恐怕就是你很努力地學(xué)習(xí),但是你從來不參加考試,所以在面試中 UI 視圖一定會涉及,而具體面試都面啥,主要有以下幾個方面,如下圖 :

本文主要就是從 UITableView、事件傳遞、視圖響應(yīng)、圖像顯示原理、卡頓掉幀、異步繪制和繪制原理以及離屏渲染 6 大塊進(jìn)行相關(guān)的講解。

1. UITableView

重用機制

上圖灰色部分就是tableView 中已經(jīng)出現(xiàn)過得 A1 cell,當(dāng)滑動不在屏幕顯示時,將其放到重用池,當(dāng)再次劃出時從重用池里面取出,這樣避免頻繁創(chuàng)建,消耗內(nèi)存和CPU,從而引起卡頓,如果繼續(xù)向上滑動 A7 就將會從重用池里面取出,這個就是 tableView 的重用機制

數(shù)據(jù)源同步

數(shù)據(jù)源同步

數(shù)據(jù)源同步的問題多出現(xiàn)在新聞、資訊類 App中,對當(dāng)前顯示的內(nèi)容進(jìn)行刪除操作的同時,進(jìn)行 loadmore 操作,loadmore 操作時,會將當(dāng)前的 data 進(jìn)行 copy,loadmore 是在子線程進(jìn)行處理,當(dāng)服務(wù)器返回結(jié)果時,那么 loadmore 將原來的 data 和現(xiàn)在新的 data 合并一起,返回主線程data,這個時候,刪除操作已經(jīng)完成,而我們再次 reload 的時候,發(fā)現(xiàn)刪除的數(shù)據(jù),這個時候又顯示了,這個就是數(shù)據(jù)源同步的問題。這種問題解決方案有兩種

  • 并發(fā)訪問解決方案

    在主線程執(zhí)行刪除的操作的時候,記錄下來要刪除的數(shù)據(jù),然后等到子線程 loadmore 解析完成,回到主線程,將返回的新 data,再次刪除即可,然后 reloadUI 即可

  • 串行訪問解決方案

    串行就是,刪除操作放到串行隊列中,當(dāng)刪除完成以后將數(shù)據(jù)傳到子線程中進(jìn)行 loadmore 等操作,子線程完成數(shù)據(jù)的解析將數(shù)據(jù)傳到主線程從而 reloadUI,這個也可以解決,但是會更加耗時,但是卻比第一種更加節(jié)約內(nèi)存,具體情況具體分析,犧牲時間換取空間

2. 事件傳遞與事件響應(yīng)

UIView和CALayer的關(guān)系

  • UIView 為其提供內(nèi)容,以及負(fù)責(zé)處理觸摸等事件,參與響應(yīng)鏈
  • CALayer 負(fù)責(zé)顯示內(nèi)容的 contents
    深入問答:為什么會這么設(shè)計?
    遵循程序設(shè)計的原則:\color{red}{單一職責(zé)原則}

事件響應(yīng)流程

事件響應(yīng)流程

點擊屏幕,事件響應(yīng)的核心其實方法就是

- (nullable UIView *)hitTest:(CGPoint)point withEvent:(nullable UIEvent *)event;   // recursively calls -pointInside:withEvent:. point is in the receiver's coordinate system
- (BOOL)pointInside:(CGPoint)point withEvent:(nullable UIEvent *)event;   // default returns YES if point is in bounds

第一個方法返回的是一個UIView,是用來尋找最終哪一個視圖來響應(yīng)這個事件;第二個方法是用來判斷某一個點擊的位置是否在視圖范圍內(nèi),如果在就返回YES

流程描述
  • 我們點擊屏幕產(chǎn)生觸摸事件,系統(tǒng)將這個事件加入到一個由UIApplication管理的事件隊列中,UIApplication會從消息隊列里取事件分發(fā)下去,首先傳給UIWindow
  • 在UIWindow中就會調(diào)用hitTest:withEvent:方法去返回一個最終響應(yīng)的視圖
  • 在hitTest:withEvent:方法中就回去調(diào)用pointInside: withEvent:去判斷當(dāng)前點擊的point是否在UIWindow范圍內(nèi),如果是的話,就會去遍歷它的子視圖來查找最終響應(yīng)的子視圖
  • 遍歷的方式是使用倒序的方式來遍歷子視圖,也就是說最后添加的子視圖會最先遍歷,在每一個視圖中都回去調(diào)用它的hitTest:withEvent:方法,可以理解為是一個遞歸調(diào)用
  • 最終會返回一個響應(yīng)視圖,如果返回視圖有值,那么這個視圖就作為最終響應(yīng)視圖,結(jié)束整個事件傳遞;如果沒有值,那么就會將UIWindow作為響應(yīng)者
hitTest:withEvent: 方法調(diào)用
調(diào)用流程
  • 首先會判斷當(dāng)前視圖的hiden屬性、是否可以交互以及透明度是否大于0.01,如果滿足條件則進(jìn)入下一步,否則返回nil
  • 調(diào)用pointInside: withEvent:方法來判斷這個點是否在當(dāng)前視圖范圍內(nèi),如果滿足條件則進(jìn)入下一步,否則返回nil
  • 然后以倒序的方式遍歷它的子視圖,在每個子視圖中去調(diào)用hitTest:withEvent:方法,如果有一個子視圖返回了一個最終的響應(yīng)視圖,那么就將這個視圖返回給調(diào)用方;如果全部遍歷完成都沒有找到一個最終的響應(yīng)視圖,因為點擊位置在當(dāng)前視圖范圍內(nèi),就將當(dāng)前視圖作為最終響應(yīng)視圖返回

3. 圖像顯示原理

圖像顯示原理 1

圖像顯示原理則總線連接 CPU 和 GPU 協(xié)同工作,CPU 提交位圖,GPU進(jìn)行位圖圖層渲染和紋理合成,最終將結(jié)果,提交到幀緩沖區(qū),然后由幀緩沖區(qū)提交到視頻控制器,最后顯示在屏幕上
在 iOS 開發(fā)上, 顯示的流程則是創(chuàng)建一個UIView,然后調(diào)用CALayer,生成要顯示的內(nèi)容,最后經(jīng)過 drawRect繪制,提交到 CoreAnimation 生成位圖,以上部分則是 CPU 的工作;然后經(jīng)過 GPU 的 OpenGL管線渲染生成結(jié)果,最后顯示在屏幕上

CPU 工作

cpu 的主要工作就是 UI 布局,frame 計算、文本計算、繪制、圖片編碼器、最后提交位圖


圖像顯示原理 2
CPU 1
CPU 2

CPU 3

CPU 4

GPU
GPU

GPU 主要是對 CPU 提供過來的位圖頂點著色、圖元裝配、光柵化、片段著色、片段處理,最后將結(jié)果提交到幀緩存區(qū)

4. 卡頓&掉幀

卡頓&掉幀
為什么會掉幀和卡頓?

頁面滑動一般就是每一秒鐘會有 60 幀的畫面更新,基于此就是每隔 16.7ms 也就是 1/60就有一幀畫面刷新,在這 16.7ms 內(nèi)就需要 CPU 和 GPU協(xié)同工作,產(chǎn)生一幀的數(shù)據(jù)。如果當(dāng)CPU消耗的時間過長,或者 GPU 渲染的時間過長,產(chǎn)生一幀的數(shù)據(jù)耗時超過 16.7ms,那么在下一個 Vsyc 信號到來之前,當(dāng)前畫面沒有準(zhǔn)備好,那么就會產(chǎn)生掉幀,而在肉眼看來就是卡頓。

滑動優(yōu)化方案
  • CPU優(yōu)化
    • 對象創(chuàng)建、調(diào)整、銷毀(子線程)
    • 預(yù)排版(布局計算、文本計算)放到子線程處理
    • 預(yù)渲染(文本的異步繪制、圖片解碼等)
  • GPU優(yōu)化
    其實就是避免離屏渲染
    • 紋理渲染
    • 視圖混合

5. 繪制原理&異步繪制

UIView 的繪制原理

當(dāng) UIView的控件在調(diào)用[UIView setNeedsDisplay]其實并沒有立刻執(zhí)行繪制工作,而是在當(dāng)前的 layer 上面打上一個臟標(biāo)記,接著會調(diào)用 [view.layer setNeedsDisplay], 在當(dāng)前 runloop 將要結(jié)束以后,則會調(diào)用 [CALayer dispaly]這個方法進(jìn)行繪制


系統(tǒng)繪制流程
系統(tǒng)繪制流程
流程描述
  • 在layer內(nèi)部會創(chuàng)建一個backing store,我們可以理解為CGContextRef上下文
  • 判斷l(xiāng)ayer是否有delegate:
    • 如果有delegate,則會執(zhí)行[layer.delegate drawLayer:inContext](這個方法的執(zhí)行是在系統(tǒng)內(nèi)部執(zhí)行的),然后在這個方法中會調(diào)用view的drawRect:方法,也就是我們重寫view的drawRect:方法才會被調(diào)用到
    • 如果沒有delegate,會調(diào)用layer的drawInContext方法,也就是我們可以重寫的layer的該方法,此刻會被調(diào)用到
  • 最后把繪制完的backing store(可以理解為位圖)提交給GPU
異步繪制

怎么進(jìn)行異步繪制呢,其實就是基于系統(tǒng)給我們開的口子layer.delegate,如果遵從或者實現(xiàn)了displayLayer方法,我們就可以進(jìn)入到異步繪制流程當(dāng)中,在異步繪制的過程當(dāng)中通過代理負(fù)責(zé)生成 bitmap,最后將繪制的bitmap 作為 layer.contents 屬性的值


異步繪制時序圖

6. 離屏渲染

什么是在屏渲染(On-Screen Rendering)?

就是說當(dāng)前的屏幕渲染,指的是 GPU 操作發(fā)生在當(dāng)前用于顯示屏幕緩沖區(qū)進(jìn)行

什么是離屏渲染?(Off-Screen Rendering)

指的是 GPU 在當(dāng)前屏幕緩沖區(qū)以外新開辟一個緩沖區(qū)進(jìn)行渲染操作
比如說我們設(shè)置的圓角屬性,蒙層遮罩都會觸發(fā)離屏渲染

何時會觸發(fā)?
  • 設(shè)置圖層的圓角且和 maskToBounds=YES 一起使用時
  • 圖層蒙版
  • 陰影
  • 光柵化
為何要避免?
  • 創(chuàng)建新的渲染緩沖區(qū),消耗內(nèi)存
  • 上下文切換(GPU額外的開銷)
    觸發(fā)離屏渲染時,會增加 GPU 的工作量,增加 GPU 工作量可能導(dǎo)致 GPU+CPU 工作總耗時(60fps 為例)超過 16.7ms,從而造成UI卡頓 和掉幀,因為我們要避免離屏渲染

總結(jié)

以上就是我們 ui 視圖部分 iOS 面試中常問到的。高頻的題目有

  • 系統(tǒng)事件的 UI 的傳遞機制是怎樣的?
  • 使 UITableView滑動更加流暢的方案或者思路有哪些?
  • 什么是離屏渲染?
  • UIView 和 CALayer之間的關(guān)系是怎么樣的?

參考文章

http://www.itdecent.cn/p/2c16077b50f8
http://www.itdecent.cn/p/254ef1640f68

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

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