iOS UI视图面试相关
UITableVIew相关
- 重用机制
objective-c
cell = [tableView dequeueReusableCellWillIdentifier:identifer];

其中A2、A3、A4、A5是完全显示在屏幕,A2、A6显示部分,A1和A7不在显示范围内,假如现在是从下滑时的结果,在A1消失时会被放入到重用池,在A7显示时从重用池里取一个cell来使用,如果cellA1-A7都是一个identifer,那么A7会拿到A1的cell来进行重用
复用池的实现:
objective-c
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
// 实现重用机制的类
@interface ViewReusePool : NSObject
// 从重用池当中取出一个可重用的view
- (UIView *)dequeueReusableView;
// 向重用池当中添加一个视图
- (void)addUsingView:(UIView *)view;
// 重置方法,将当前使用中的视图移动到可重用队列当中
- (void)reset;
@end
objective-c
#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之间的关系是怎样的?