UI相关

作者: 天明天 | 来源:发表于2020-05-22 09:14 被阅读0次

重用机制、数据源同步、图像显示、UI掉帧卡顿、UIView的异步绘制、离屏渲染

截屏2021-01-30 下午5.13.21.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.数据源同步问题

问题描述:


截屏2020-05-18 上午10.38.27.png

主线程删除 数据,子线程添加更多数据,实现数据的同步
解决方案:

  • 并发访问,数据拷贝


    截屏2020-05-18 上午10.42.55.png
  • 串行访问


    截屏2020-05-18 上午10.46.23.png

各自都有优缺点:一个需要比较大的内存开销,需要拷贝数据,一个主线程需要等待。

3.视图的事件传递以及相应

(1) UIViewCALayer 的区别与联系

截屏2020-05-18 上午10.51.32.png
  • UIViewCALayer 提供内容、以及负责处理触摸事件,参与响应链
  • 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) 当屏渲染与离屏渲染

截屏2020-05-18 下午3.12.00.png
GPU相关
(2)触发条件
截屏2020-05-18 下午3.14.16.png

圆角要与maskToBounds 一起设置才起作用
(3)为何要避免离屏渲染
离屏渲染 会增加GPU的工作量,间接导致CPU 与GPU的工作耗时大于16.7ms,导致UI的卡顿跟掉帧

截屏2020-05-18 下午3.18.23.png
截屏2021-01-30 下午5.10.49.png

相关文章

  • UI相关

    1.NAV和ToolBar && Tabbar icon尺寸

  • UI相关

    ScrollView内容位置向下偏移或不存在 原因分析: NavigationController导致scroll...

  • UI相关

    1. button 图标和文字位置设置 //图标[button setImage:[UIImage imageNa...

  • UI相关

    1.$\color{red}{UIView和CAlayer的区别?}$ 每个UIView内部都有一个CALayer...

  • UI相关

    重用机制、数据源同步、图像显示、UI掉帧卡顿、UIView的异步绘制、离屏渲染 1.重用机制 cell = [ta...

  • 网易UI微专业课笔记——UI零基础预习课程

    一、UI设计概述 UI相关术语解释 UI(User Interface)用户界面 GUI(Graphical Us...

  • 多线程相关

    引文: 多线程相关 OC 语言相关 内存管理相关 UI视图相关 RunLoop相关 GCD NSOperation...

  • iOS 常用第三方库统计

    Demo集合 动画相关 UI相关 图片相关 数据库相关 播放器相关 工具类 Helper

  • # UI视图相关

    UI视图相关 UITableView 事件传递&视图响应 图像显示原理 卡顿&掉帧 绘制原理&异步绘制 离屏渲染 ...

  • UI视图相关

    1.事件传递及相应 (1)UIView及CALayer UIView为其提供内容,以及负责触摸等事件,参与视图事件...

网友评论

      本文标题:UI相关

      本文链接:https://www.haomeiwen.com/subject/qmnyohtx.html