iOS UI視圖面試相關
UITableVIew相關
- 重用機制
cell = [tableView dequeueReusableCellWillIdentifier:identifer];

其中A2、A3、A4、A5是完全顯示在屏幕,A2、A6顯示部分,A1和A7不在顯示范圍內,假如現在是從下滑時的結果,在A1消失時會被放入到重用池,在A7顯示時從重用池里取一個cell來使用,如果cellA1-A7都是一個identifer,那么A7會拿到A1的cell來進行重用
復用池的實現:
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
// 實現重用機制的類
@interface ViewReusePool : NSObject// 從重用池當中取出一個可重用的view
- (UIView *)dequeueReusableView;// 向重用池當中添加一個視圖
- (void)addUsingView:(UIView *)view;// 重置方法,將當前使用中的視圖移動到可重用隊列當中
- (void)reset;@end
#import "ViewReusePool.h"@interface ViewReusePool ()
// 等待使用的隊列
@property (nonatomic, strong) NSMutableSet *waitUsedQueue;
// 使用中的隊列
@property (nonatomic, strong) NSMutableSet *usingQueue;
@end@implementation ViewReusePool- (id)init{self = [super init];if (self) {_waitUsedQueue = [NSMutableSet set];_usingQueue = [NSMutableSet set];}return self;
}- (UIView *)dequeueReusableView{UIView *view = [_waitUsedQueue anyObject];if (view == nil) {return nil;}else{// 進行隊列移動[_waitUsedQueue removeObject:view];[_usingQueue addObject:view];return view;}
}- (void)addUsingView:(UIView *)view
{if (view == nil) {return;}// 添加視圖到使用中的隊列[_usingQueue addObject:view];
}- (void)reset{UIView *view = nil;while ((view = [_usingQueue anyObject])) {// 從使用中隊列移除[_usingQueue removeObject:view];// 加入等待使用的隊列[_waitUsedQueue addObject:view];}
}@end
- 數據源同步

刪除一般是在主線程操作,刪除時往往會觸發后向刷新LoadMore,這就涉及到多線程對共享數據的訪問,需要考慮數據源同步問題,解決方案:
- 并發訪問、數據拷貝

在子線程請求數據時,主線程做了刪除操作,導致數據不同步
主線程記錄刪除操作,在子線程中返回數據前同步刪除操作

- 串行訪問
子線程進行網絡請求、數據解析,網絡請求回來時在串行隊列去進行新增數據預排版(子線程當中),在這期間主線程刪除了某個數據,在串行隊列當中前一個block完成之后去執行同步主線程發送過來的任務同步數據刪除,然后再去回到主線程更新UI。

事件傳遞 & 視圖響應
UIVIew和CALayer的區別
- UIView為其提供內容,以及負責處理觸摸等事件,參與響應鏈
- CALayer負責顯示內容contents
UIView實際上是對CALayer的輕量級封裝。每個UIView都有一個關聯的CALayer作為其backing store,負責實際的渲染工作,而UIView則處理用戶交互和事件響應。CALayer通過contents屬性提供要顯示的位圖信息,UIView繼承自UIResponder,而CALayer繼承自NSObject,UIView主要負責處理用戶交互,如觸摸、點擊和拖動等事件。而CALayer則專注于渲染和動畫處理
事件傳遞與視圖響應鏈
假如點擊了c2的空白區域,系統最終怎么樣找到響應視圖為c2的?

事件傳遞:尋找最佳響應者(Hit-Testing)

事件傳遞是從父控件到子控件的正向傳遞過程(點擊屏幕 → UIApplication → UIWindow → 父視圖 → 子視圖
),目標是找到最合適的視圖(Hit-Test View)處理觸摸事件。
-
核心方法
hitTest:withEvent:
:遞歸調用子視圖的該方法,返回最終處理事件的視圖。pointInside:withEvent:
:判斷觸摸點是否在視圖范圍內,是hitTest
的底層依賴方法
-
傳遞規則
- 從
UIWindow
開始,遍歷子視圖(從后往前,即最上層的子視圖優先)。 - 若視圖滿足以下條件,則繼續向其子視圖傳遞:
userInteractionEnabled = YES
hidden = NO
alpha > 0.01
- 觸摸點在視圖范圍內(通過
pointInside
驗證)。
- 若當前視圖無子視圖或子視圖不滿足條件,則自身成為最佳響應者。
- 從
-
特性
- 即使父視圖是最佳響應者,仍會遞歸調用所有子視圖的
hitTest
方法(確保無更合適的子視圖)5。 - 可重寫
hitTest:withEvent:
實現特殊事件攔截(如擴大按鈕點擊區域)。
- 即使父視圖是最佳響應者,仍會遞歸調用所有子視圖的
響應鏈:事件處理流程
當最佳響應者無法處理事件時,事件沿響應鏈反向傳遞(從子控件到父控件),直至被處理或丟棄。
-
響應鏈構成
響應鏈由UIResponder
對象(UIView
、UIViewController
等)通過nextResponder
連接:最佳響應視圖 → 父視圖 → ... → 控制器 → UIWindow → UIApplication → AppDelegate
- 視圖的
nextResponder
:若視圖是控制器的根視圖,則指向控制器;否則指向父視圖。 - 控制器的
nextResponder
:指向其視圖的父視圖或UIWindow
。
- 視圖的
-
事件響應邏輯
- 最佳響應者優先處理事件(如實現
touchesBegan:withEvent:
)。 - 若未處理,調用
[super touchesBegan...]
將事件傳遞給nextResponder
。 - 若響應鏈中所有對象均未處理,事件被靜默丟棄(不會崩潰)。
- 最佳響應者優先處理事件(如實現
UI卡頓掉幀

通常情況下,屏幕刷新率為60Hz,意味著每秒需要渲染60幀畫面,即每幀的渲染時間不應超過16.7毫秒。如果某一幀的渲染時間超過了16.7毫秒,就會導致卡頓或掉幀現象,影響用戶體驗
繪制原理

為什么調用[UIView setNeedsDisplay]并沒有立即進行視圖的繪制工作?
在當前Runloop將要結束才會介入UI視圖的繪制流程當中
異步繪制
- 異步繪制:通過實現
CALayer
的代理方法displayLayer
,在子線程中生成位圖,減少主線程的負擔。

離屏渲染
在屏渲染:也為當前屏幕渲染,GPU的渲染操作是在當前用于顯示的屏幕緩沖區中進行
離屏渲染:GPU在當前屏幕緩沖區以外新開辟一個緩沖區進行渲染操作
避免離屏渲染:盡量避免使用 layer 的圓角、邊框、陰影等屬性,這些屬性會觸發離屏渲染,增加 GPU 的負擔,可能導致GPU和CPU的總耗時超過16.7ms,可能會掉幀,可以在后臺線程預先繪制好對應內容,減少 GPU 的工作量。
常見的觸發離屏渲染的屬性
- 圓角 (
cornerRadius
):- 當設置
layer.cornerRadius
屬性并且masksToBounds
為YES
時,會觸發離屏渲染。這是因為masksToBounds
會應用到所有的圖層上,需要在離屏緩沖區中進行圓角裁剪處理。 - 如果
layer
只有一個圖層且沒有content
,設置cornerRadius
和masksToBounds
不會觸發離屏渲染。
- 當設置
- 陰影 (
shadow\*
):- 當設置
layer.shadow*
屬性時,如果陰影路徑(shadowPath
)沒有設置,系統需要離屏渲染來計算陰影。陰影需要在所有內容繪制完成后根據外輪廓進行繪制,因此需要在離屏緩沖區中進行處理。
- 當設置
- 圖層蒙版 (
mask
):- 使用
layer.mask
或者layer.masksToBounds
時會觸發離屏渲染。這是因為蒙版需要在離屏緩沖區中進行剪裁處理。
- 使用
- 組透明度 (
group opacity
):- 設置
layer.allowsGroupOpacity
或者layer.opacity
時,如果opacity
小于1,會觸發離屏渲染。這是因為透明度需要在離屏緩沖區中進行混合處理。
- 設置
- 光柵化 (
shouldRasterize
):- 開啟
layer.shouldRasterize
會觸發離屏渲染。光柵化會將layer
的渲染結果保存在離屏緩沖區中,以便在后續幀中復用,減少重復渲染的開銷
- 開啟
滑動優化方案
基于tableview、scrollview的滑動優化方案
- CPU方面
可以在子線程進行一些對象創建、調整、銷毀、包括進行預排版(布局計算、文本計算)、預渲染(文本等異步繪制、圖片編解碼等)
- GPU方面
避免離屏渲染,盡量避免使用 layer 的圓角、邊框、陰影等屬性,這些屬性會觸發離屏渲染,增加 GPU 的負擔
異步繪制:通過實現 CALayer
的代理方法 displayLayer
,在子線程中生成位圖,減少主線程的負擔。例如,微博的頭像在下載后會在后臺線程預先渲染為圓形并保存到緩存中,這樣可以減少主線程的渲染壓力。
復用機制:不要一次性創建所有子視圖,而是在需要時創建,并復用它們。這樣可以減少內存分配的開銷,節省內存空間。UITableView 和 UICollectionView 的復用機制就是一個很好的例子。
懶加載:把創建對象的時機延后到不得不需要它們的時候,減少初始加載時間。
按需加載:在滑動過程中,按需加載對應的內容。例如,當目標行與當前行相差超過指定行數時,只在目標滾動范圍的前后指定幾行加載,減少不必要的加載操作。
自動加載更新數據:在滾動到特定位置時,自動加載更多數據,減少用戶等待時間。
面試問題總結:
- 系統的UI事件傳遞機制是怎樣的?
- 使UITableView滾動更流暢的方案或思路都有哪些?
- 什么是離屏渲染?
- UIView和CALayer之間的關系是怎樣的?