iOS晋级知识汇总(一)UI优化

UItableview相关

重用机制

通过下面方法得到cell:

cell =  [self.TableView dequeueReusableCellWithIdentifier:identifer];

一般在iOS中,tableview的重用机制,我们在- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath方法中,首先通过dequeueReusableCellWithIdentifier获取重用池中的cell,如果获取的cell是nil那么就重新创建一个新的cell,那么重用机制的原理是什么呢?

  • 首先我们刷新数据源的时候,整个重用池里面的cell跟屏幕的高度以及cell的个数有关,屏幕总共能够放下多少个cell那么重用池中就会创建多少个cell比如说:
    • 当前屏幕中共放下了6个cell,当向上滑动的时候,cell1被隐藏了,相对的cell7从最下面露了出来,这时候就用到了重用机制,Cell1被隐藏时,Cell1的真的对象,就会放入到重用池当中,并且是待重用的状态,而cell7出来以后需要一个新的对象,重用池中有一个待使用的对象,正好被cell7使用,以此类推,随着界面滑动,cell的重用都是按照这个过程来进行的。

数据源同步

新闻、咨询类的App当中,数据源往往有一个删除操作、还同时需要LoadMore

  • 删除操作主线程、LoadMore在子线程
  • 这就需要考虑数据源的同步问题。

解决方案一般有2种:

  • 并发访问 数据拷贝
    • 需要记录操作,在子线程返回的数据源在同步记录
    • 在并发任务中没有用户操作延迟问题,对内存会增加
并发方式数据同步.png
  • 串行任务
    • 子线程和主线程任务同步
    • 要求如果在子线程处理任务比较繁重的时候,删除操作就会出现延迟
串行队列数据源同步.png

事件传递和视图响应

UIView和CALayer的关系和区别:

UIView中的一个属性layer是CALayer类型变量,UIView中的属性backgroundColor实际上是layer中同名属性的一个包装,UIView的显示部分是由CALayer的contents来决定,contents所对应的是backking store,实际上是bitmap类型的一个位图。

  • UIView为其提供内容,以及负责处理触摸等事件,参与响应链
  • CALayer负责显示内容contents

为什么UIView为其提供内容,以及负责处理触摸等事件,参与响应链,CALayer负责显示内容contents?

设计原则,符合单一原则 ,职责分工。

事件传递机制

事件传递最主要的两个API:

- (nullable UIView *)hitTest:(CGPoint)point withEvent:(nullable UIEvent *)event;
- (BOOL)pointInside:(CGPoint)point withEvent:(nullable UIEvent *)event;
  • hitTest最终是哪个视图响应这个事件就返回这个UIView
  • pointInside判断某一个点击的位置是否在当前视图范围内,如果在就返回YES,不在返回NO

事件传递的流程:

事件传递过程.png

hitTest系统内部的调用过程

  • 1、 首先判断view的hidden(是否隐藏)、userinteractionEnabled(是否可以交互)和Alpha(透明度)
    • 如果不可以直接返回nil
    • 如果可以交互那么就调用走2、
  • 2、 调用pointInside来判断这个点是否在当前视图范围内
    • 如果不在当前视图,也会返回nil
    • 如果在当前视图,就走3、
  • 3、 倒序的形式便利当前view的子视图,便利过程中会调用所有子视图的hitTest的方法。
    • 如果便利结束都没有找到对应的子视图去响应这个事件,如果当前位置在当前的视图范围内,就会把当前视图做为最终事件响应视图返回给调用方
    • 假如子视图中的某个hitTest返回了最终的UIView,那么这个视图就做为最终事件响应视图,返回给调用方

例子:

方形的but,我们只需要在圆形范围响应事件:

步骤:

  • 首先重写hittestpointInside方法
    • hittest方法实现:
      • 1、判断userInteractionEnabled、isHidden、alpha是否允许,如果不允许直接返回nil
      • 2、调用pointInside方法判断点击事件是否在当前视图,如果不在返回nil
      • 3、如果在当前视图,倒序遍历子视图并调用hittest方法,一直便利到最终视图,返回调用者
    • pointInside方法中,根据这个point来判断是否在圆形空间内,如果在返回YES,如果不在返回NO

视图响应流程

uibutton->UIView->可能还存在view->uiwindow->UIApplication->UIApplicationDelegate
uibutton->UIView->可能还存在view->UIViewcontroller->uiwindow->UIApplication->UIApplicationDelegate

视图响应的API

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
- (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
- (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event

上述方法都是UIResponder的方法,UIView和UIViewController是继承于UIResponder,所以都有这些方法。

事件传递让我们能够找到最终的视图,而最终这个事件是否由你找到的视图来处理的话呢?那就涉及到视图响应链的一个流程机制。

流程:

  • 如果说最终视图无法响应
  • 会传递给父视图进行响应,下一个响应者
  • 如果父视图不响应那么继续传给父视图的父视图。
  • 按照上述流程一直向上传递,直到传递给UIApplicationDelegate
  • 假如一直到UIApplicationDelegate还没有找到?
    • 忽略这个响应
响应链.png

图像显示原理

CPU和GPU两个硬件都是通过总线连接起来的。CPU输出的结果一般都是位图,在经过总线在合适的时机上传给GPU, GPU拿到位图以后,会做图层的一些渲染,包括纹理的合成之后会把结果放到Frame Buffer(缓冲区)由视频控制器根据信号在指定时间之前,去提取在帧缓冲区中的屏幕显示内容然后最终显示到手机屏幕上。

图像显示原理.png

UIView显示部分是由CALayer来负责的,而CALayer中有个属性contents就是最终要绘制到手机屏幕上的一个位图,比如说是一个UILabel,那么contents中最终放的结果就是关于helloworld一个文字的位图,然后系统会在合适的时机回调给UIView的一个drawRect方法,让程序员在此基础之上去绘制自己一些想要的自定义绘制的内容。

绘制好的位图最终会由Core Animation这个框架提交给GPU中的OpenGL(ES)渲染管线,进行最终的位图的渲染包括纹理的合成然后显示在手机屏幕上。

UIView的显示原理

CPU工作

Layout Display(显示) Prepare(准备) commit
UI布局、文本计算 绘制(drawRect) 图片编解码 提交位图

GPU渲染管线

实际上即使OpenGL的渲染管线,这个过程:

  • 顶点着色(对位图的一个处理)
  • 图元装配
  • 光栅化
  • 片段着色
  • 片段处理

做完以后,最终把像素点提交到FrameBuffer帧缓冲区中,由视频控制器在VSync信号到来之前去FrameBuffer帧缓冲区当中提取最终要显示在屏幕上的内容,这就构成了OpenGL的显示原理

卡顿和掉帧

UI卡顿和掉帧的原因

一般来说页面滑动的流畅性是60FPS,指的就是每一秒钟会有60帧的画面更新,我们在人眼上面所看到的就是流畅的效果,那基于此每隔16.7ms(1/60)就要产生一帧的画面,那么在16.7ms之内需要CPU和GPU共同协同完成产生最终的一帧的数据。

卡顿掉帧的原因.png

比如说CPU花费一定的时间(文本的布局,UI的计算包括一些视图的绘制,以及图片解码然后最终得到的位图提交给GPU),再由GPU进行相应的图层的合成、管理、渲染,准备好下一帧的画面,然后在下一帧的VSync信号到来的时候,就可以显示这个画面。

如果说CPU花费的时间特别长的话,那么留给GPU的时间就会减少,如果GPU进行相应的图层的合成、管理、渲染全部准备完毕可能就要总时间超过16.7ms,那么在下一帧VSync信号到来的时候
没有准备好这一帧的画面,那就由此产生掉帧,由此产生一个卡顿。

那么上述就可以总结UI卡顿掉帧的原因。

在规定的16.7ms之内在下一帧VSync到来之前并没有CPU和GPU共同完成下一帧画面的合成,于是就会造成卡顿掉帧。

滑动优化方案

基于TableView和ScrollView都有哪些优化方案,你又是怎么样做的?

  • CPU:减轻CPU所做的工作时长、包括它的压力来达到优化的效果。

    • 对象创建、调整、销毁放入子线程
    • 预排版(布局计算、文本计算)放入到子线程去做
    • 上述2个方式主线程就可以有更多的时间去响应用户的交互
    • 预悬案(文本等异步绘制,图片编解码等)
  • GPU

    • 纹理渲染(masksToBounds、圆角、阴影、蒙层都会涉及到GPU的离屏渲染)这样工作量就会加大基于这种情况我们就可以对GPU进行优化,可以依靠CPU的异步绘制来减轻GPU的离屏渲染压力。
    • 视图混合(视图层级非常复杂,有多个视图层层叠加,那么GPU就要对这些视图进行合成,每一个像素点对应的像素值他需要做大量的计算,那么在一定程度上减轻视图层级的复杂性,那也可以减轻GPU的压力,也包括CPU的异步绘制来达到提交的位图本身就是层级少的视图,这样也可以减轻GPU的压力)。

绘制原理和异步绘制

UIView的绘制原理

当我们调用[UIView setNeedsDisplay]的时候,实际并没有立刻发生当前视图的绘制工作,而是在之后的某个时机才会进行当前UIView的绘制工作,基于这个问题,这是为什么?

  • 1、当调用这个方法[UIView setNeedsDisplay]这个方法
  • 2、系统会自动调用[view.layer setNeedsDisplay]这个方法

相当于在view.layer上面打上了一个标记,然后会在当前Runloop将要结束的时候调用view.layer[view.layer display]然后才能进入到当前视图的真正的绘制工作。

[view.layer display]中的方法会判断view.layer.delegate是否会响应displaylayer方法,假如不响应这个方法,就会进入到系统的绘制流程,
如果说view.layer.delegate响应displaylayer方法,那么就会进入到异步绘制入口。

UIView绘制的原理过程.png

系统绘制的流程

  • 在CAlayer内部会创建创建一个backingstore(CGContextRef),我们可以通过drawRect方法中可以通过上下文堆栈当中取出栈顶的(context)CGContextRef,也可以说是backingstore,然后判断是否有代理:

  • 如果没有代理会调用[view.layer drawInContext:]方法。

  • 如果有代理会调用view.layer.delegate drawLayer: inContext:方法,做当前视图的绘制工作,这是系统内部做的事情,然后在合适的时间回调给我们方法[view drawRect:]

[view drawRect:]默认是什么都不做,在系统的绘制的基础上再做其他的工作

无论是那种哪种方式最终都是CALayer来上传对应的backingstore到GPU,最后结束系统绘制的流程

系统的绘制原理.png

如何实现异步绘制

异步绘制基于系统给开发者开放一个API[view.layer.delegate displayLayer:],如果我们实现了displayLayer:的方法,就可以进入到异步绘制的一个流程中。

  • 代理负责生成对应的Bitmap位图
  • 设置该bitmap作为layer.contents属性的值

原理流程

  • 首先调用了[view setNeedsDisplay]的方法之后
  • 在当前RunLoop将要结束的时候会由系统调用视图所对应layer的[view.layer display]
  • 如果我们实现了这个代理方法displayLayer:,会通过子线程的切换在子线程中去做一个位图的绘制,此时主线程可以做其他的操作
    • 子线程的主要工作
    • CGBitmapContextCreate (core graphic的一个函数来创建一个位图的上下文)
    • CoreGraphic API 通过这个API可以做当前UI控件的一些绘制工作
    • CGBitmapContextCreateImage 通过这个函数来根据上下文生成一张cgimage的图片
  • 最后回到主队列,提交这个位图,设置当前视图的layer设置其setContents这样就完成了一个UI控件的异步绘制过程
UI异步绘制的一个原理.png

离屏渲染

什么是离屏渲染?你是怎么理解的?

  • On-Screen Rendering

    • 意为当前屏幕渲染,指的是GPU的渲染操作是在当前用于显示的屏幕缓冲区中进行的
    • 在屏渲染是GPU层面中的一个概念
  • Off-Screen Rendering

    • 意为离屏渲染,指的是GPU在当前屏幕缓冲区以外新开辟的一个缓冲区进行渲染操作
    • 离屏渲染起源于GPU上面

当我们指定了UI视图的某些属性,标记为在未预合成之前不能用于当前屏幕上直接显示的时候,就会触发离屏渲染。

离屏渲染,何时会触发?

  • 圆角(同时要设置maskToBounds一起使用的时候才能触发)
  • 图层蒙版
  • 阴影
  • 光栅化

为何要避免离屏渲染?

触发离屏渲染的时候,会增加GPU的工作量,而增加了GPU的工作量很有可能导致了CPU和GPU工作耗时加起来的总耗时超出了16.7ms,那么就可能导致UI的卡顿和掉帧,所以我们需要避免离屏渲染。

系统的UI事件传递机制是怎么样的?

hittest和PointInside的流程

使UITableView滚动更流程的方案或思路都有哪些?

对于CPU在于子线程中对象的创建、调整和销毁、包括进行预排版以及图片的解码,采用异步绘制方案。这些都是对于UITableView滑动优化的方案

什么是离屏渲染?

离屏渲染起源于GPU,在当前屏幕缓冲区之外新开辟一个缓存区进行渲染操作就是离屏渲染,

UIView和CALayer之间的关系是怎么样的?

UIView是专门负责事件传递和视图响应的
而CALyaer全权负责视图的显示工作。

为什么要区分UIView和CALayer的职责划分

用到了六大设计原则的单一原则。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容