重用机制、数据源同步、图像显示、UI掉帧卡顿、UIView的异步绘制、离屏渲染
![](https://img.haomeiwen.com/i1977395/041d666e6c5cd216.png)
1.重用机制
cell = [tableView dequeueResuableCellWithIdentifer:@"cell_identifer"]
例子:字母索引条的应用
创建重用池
.h
// 实现重用机制的类
@interface ViewReusePool : NSObject
// 从重用池当中取出一个可重用的view
- (UIView *)dequeueReusableView;
// 向重用池当中添加一个视图
- (void)addUsingView:(UIView *)view;
// 重置方法,将当前使用中的视图移动到可重用队列当中
- (void)reset;
@end
.m
@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];
}
}
2.数据源同步问题
问题描述:
![](https://img.haomeiwen.com/i1977395/5d1db9e423f2844a.png)
主线程删除 数据,子线程添加更多数据,实现数据的同步
解决方案:
-
并发访问,数据拷贝
截屏2020-05-18 上午10.42.55.png
-
串行访问
截屏2020-05-18 上午10.46.23.png
各自都有优缺点:一个需要比较大的内存开销,需要拷贝数据,一个主线程需要等待。
3.视图的事件传递以及相应
(1) UIView
跟 CALayer
的区别与联系
![](https://img.haomeiwen.com/i1977395/21b14370181c9d2e.png)
-
UIView
为CALayer
提供内容、以及负责处理触摸事件,参与响应链 -
CALayer
负责内容的显示
原因:符合单一职责的设计原则,各司其职
(2)事件传递机制与相应 - 主要方法:
- (UIview*)hitTest:(CGPoint)point withEvevt:(UIEvent*)event;
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent*)event;
-
传递流程:
截屏2020-05-18 上午11.01.02.png
注意是 倒序便利子视图,后添加的先调用其对应
hitTest
方法
-
hitTest:withEvent
方法的内部实现
截屏2020-05-18 上午11.03.29.png
底层具体实现如下 :
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
// 1.判断当前控件能否接收事件
if (self.userInteractionEnabled == NO || self.hidden == YES || self.alpha <= 0.01) return nil;
// 2. 判断点在不在当前控件
if ([self pointInside:point withEvent:event] == NO) return nil;
// 3.从后往前遍历自己的子控件
NSInteger count = self.subviews.count;
for (NSInteger i = count - 1; i >= 0; i--) {
UIView *childView = self.subviews[i];
// 把当前控件上的坐标系转换成子控件上的坐标系
CGPoint childP = [self convertPoint:point toView:childView];
UIView *fitView = [childView hitTest:childP withEvent:event];
if (fitView) { // 寻找到最合适的view
return fitView;
}
}
// 循环结束,表示没有比自己更合适的view
return self;
}
参考链接:https://www.jianshu.com/p/f55b613b564e
(3) 实际应用:
-
方形按钮 指定区域接受时间响应
截屏2020-05-18 上午11.20.32.png
按钮 点击圆形响应,其他地方不响应方法:
//重写hitTest方法,重写pointInstend 方法
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
if (!self.userInteractionEnabled ||
[self isHidden] ||
self.alpha <= 0.01) {
return nil;
}
if ([self pointInside:point withEvent:event]) {
//遍历当前对象的子视图
__block UIView *hit = nil;
[self.subviews enumerateObjectsWithOptions:NSEnumerationReverse usingBlock:^(__kindof UIView * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
// 坐标转换
CGPoint vonvertPoint = [self convertPoint:point toView:obj];
//调用子视图的hittest方法
hit = [obj hitTest:vonvertPoint withEvent:event];
// 如果找到了接受事件的对象,则停止遍历
if (hit) {
*stop = YES;
}
}];
if (hit) {
return hit;
}
else{
return self;
}
}
else{
return nil;
}
}
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event
{
CGFloat x1 = point.x;
CGFloat y1 = point.y;
CGFloat x2 = self.frame.size.width / 2;
CGFloat y2 = self.frame.size.height / 2;
//平方差 计算点击区域距离 中心点的距离
double dis = sqrt((x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2));
// 67.923
if (dis <= self.frame.size.width / 2) {
return YES;
}
else{
return NO;
}
}
(4)视图响应机制
主要有三种响应事件
- 主要方法
触摸事件
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event;
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event;
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event;
- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event;
传感器事件
- (void)motionBegan:(UIEventSubtype)motion withEvent:(UIEvent *)event;
- (void)motionEnded:(UIEventSubtype)motion withEvent:(UIEvent *)event;
- (void)motionCancelled:(UIEventSubtype)motion withEvent:(UIEvent *)event;
远程控制事件
- (void)remoteControlReceivedWithEvent:(UIEvent *)event;
响应者链条其实就是很多响应者对象(继承自UIResponder的对象)一起组合起来的链条称之为响应者链条
一般默认做法是控件将事件顺着响应者链条向上传递,将事件交给上一个响应者进行处理 (即调用super的touches方法)。
UIApplication-->UIWindow-->递归找到最合适处理的控件-->控件调用touches方法-->判断是否实现touches方法-->没有实现默认会将事件传递给上一个响应者-->找到上一个响应者-->找不到方法作废
一句话总结整个过程是:触摸或者点击一个控件,然后这个事件会从上向下(从父->子)找最合适的view处理,找到这个view之后看他能不能处理,能就处理,不能就按照事件响应链向上(从子->父)传递给父控件
事件的传递和响应的区别:
事件的传递是从上到下(父控件到子控件),事件的响应是从下到上(顺着响应者链条向上传递:子控件到父控件。
# [iOS 子视图超出父视图范围点击事件处理!](https://www.cnblogs.com/yujidewu/p/5684029.html)
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
UIView *view = [super hitTest:point withEvent:event];
NSLog(@"1-----%f------%f",point.x,point.y);
// 将point的x,y从以self为坐标系转换到以self.fb为坐标系进行参考
CGPoint buttonPoint = [self.fb convertPoint:point fromView:self];
NSLog(@"2-----%f------%f",buttonPoint.x,buttonPoint.y);
if ([self.fb pointInside:buttonPoint withEvent:event]) {
return self.fb;
}
return view;
}
//两者一样
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
UIView *view = [super hitTest:point withEvent:event];
if (view == nil) {
CGPoint tempoint = [self.senderBtn convertPoint:point fromView:self];
if (CGRectContainsPoint(self.senderBtn.bounds, tempoint))
{
view = self.senderBtn;
}
}
return view;
}
//此方法写在父视图的方法中
4.图像显示原理
主要用于解决UI掉帧卡段相关面试
UI显示图层流程
截屏2020-05-18 下午2.17.00.png
- CPU工作
UI 视图 展示 CPU的全部工作
Layout:UI布局,文本计算
Display: 绘制drawReact
方法
Prepare: 图片的编码解码
Commit :由 Core Animation 框架 提交 位图
截屏2020-05-18 下午2.21.02.png
- GPU的渲染
截屏2020-05-18 下午2.22.13.png
渲染完成后 提交给Frame buffer(帧缓冲区)
由V sync
信号 进行提取展示
5.UI卡顿、掉帧的原因
-
原因
截屏2020-05-18 下午2.27.32.png
主要是:CPU与GPU的处理时间超过16.7ms,导致缓冲帧没有准备好,下一次Vsync信号到来后 渲染出现延迟。
- 滑动优化方案:性能优化
(1)CPU- 对象的创建、调整、销毁 放在子线程处理
- 预排版(UI的布局计算、文本计算) 放在子线程处理
- 预渲染(文本的异步绘制、图片的编码、解码)
(2)GPU
- 减少离屏渲染(圆角、阴影、光栅化、遮罩),圆角使用
CoreGraphics
绘制 - GPU能处理的最大纹理尺寸是4096x4096,一旦超过这个尺寸,就会占用CPU资源进行处理,所以纹理尽量不要超过这个尺寸
- 尽量减少视图数量和层次
- 减少透明的视图(alpha<1),不透明的就设置opaque为YES
6.UIView的绘制原理
- 调用
[UIView setNeedDisplay]
方法 然后调用[view.layer setNeedDisplay]
,在当前RunLoop
将要结束时 调用[CALayer display]
方法
截屏2020-05-18 下午2.47.25.png
-
系统的绘制流程:
截屏2020-05-18 下午2.49.49.png
- 异步绘制
就是在方法:[layer.delegate displayLayer]
方法里面进行操作,实现代理displayerLayer
方法
截屏2020-05-18 下午2.51.26.png
-
disPlayLaber
所做事情
开辟子线程
截屏2020-05-18 下午2.55.32.png
- 位图
Bitmap
的创建 ->(CoreGraphics) API 处理 -> 生成CGImage 图片 -> 回到主队列 把图片 提交给[CALayer setContents:]
方法 完成异步绘制
截屏2020-05-18 下午2.59.22.png
7.离屏渲染相关
(1) 当屏渲染与离屏渲染
![](https://img.haomeiwen.com/i1977395/2b0f6d4cfb553981.png)
GPU
相关(2)触发条件
![](https://img.haomeiwen.com/i1977395/7d3fec07c941f626.png)
圆角要与
maskToBounds
一起设置才起作用
(3)为何要避免离屏渲染
离屏渲染 会增加GPU的工作量,间接导致CPU 与GPU的工作耗时大于16.7ms,导致UI的卡顿跟掉帧
截屏2020-05-18 下午3.18.23.png
![](https://img.haomeiwen.com/i1977395/57877df56504af6f.png)
网友评论