美文网首页iOS面试题集合
UIView 的渲染过程

UIView 的渲染过程

作者: 若水water | 来源:发表于2020-04-07 18:16 被阅读0次
    • setNeedsLayout

    标记为需要重新布局,异步调用layoutIfNeeded刷新布局,不立即刷新,在下一轮runloop结束前刷新,对于这一轮runloop之内的所有布局和UI上的更新只会刷新一次,layoutSubViews一定会被调用。

    • layoutIfNeeded

    如果有需要刷新的标记,立即调用layoutSubViews进行布局(如果没有标记,不会调用layoutSubViews)

    注意:

    • layoutIfNeeded不一定会调用layoutSubViews方法
    • setNeedsLayout一定会调用layoutSubViews方法(有延迟,下一轮runloop结束前)
    • 如果想在当前runloop中立即刷新,调用顺序是
    [self setNeedsLayout];
    [self layoutIfNeeded];
    
    • setNeedsDisplay

    和setNeedsLayout一样是异步执行的,此方法会调用drawRect方法。

    layoutSubviews 以下情况会调用
    • addsubview 会触发layoutSubviews
    • 设置View的frame 会触发layoutSubviews,当然frame的前后得不一样
    • 滚动一个uiscrollview会触发layoutSubviews
    • 旋转screen会触发父UIView上的layoutSubviews
    • 改变一个UIView的大小时,会触发父UIView上的layoutSubviews
    drawRect 在以下情况下会被调用
    • 如果UIView在初始化时没有设置rect大小,将直接导致drawRect不被自动调用。drawRect调用是在Controller-》loadview,Controller-》viewDidload两方法之后调用的,所以不用担心在控制器中,这些view的drawRect就开始画了,这样可以在控制器中设置一些值给view
    • 该方法在调用sizeToFit后呗调用,所以可以先调用sizeToFit计算出size。然后系统自动调用drawRect方法。
    • 通过设置contentModel属性值为UIViewContentModeRedraw,那么将在每次设置更改frame的时候自动调用drawRect
    • 直接设置setNeedsDisplay,或者setNeedsDisplayInRect:触发drawRect,但是有个前提条件是rect 不能为0;
    drawRect方法使用注意点
    • 使用UIView绘图,只能在drawRect方法中获取相应的contextRef并绘图。如果在其他方法中获取将会获取到一个invalidate的ref并且不能用于画图。drawRect方法不能手动调用,必须通过调用setNeedsDisplay或者setNeedsDisplayInRect,让系统自动调用该方法。
    • 若使用CALayer绘图,只能在drawInContext:中(类似于drawRect)绘制,或者在delegate中的相应方法绘制。同样也是调用setNeedsDisplay等间接调用以上方法。
    • 若要实时画图,不能使用gestureRecognizer,只能使用touchbegin等方法来调用setNeedsDisplay实时刷新屏幕。
    • sizeToFit

    这是一个UIView的方法。意思是调整和移动view 和view内部子视图的大小和位置。当你想要调整当前视图,以便他使用最合适的空间的时候用这个方法。特定的视图会根据内部需要进行尺寸调整。在特定条件下,如果view没有父视图,他会根据屏幕的bounds来进行调整。如果你需要view根据父视图来调整大小,就需要将view添加到父视图中取。
    其实说白了,就是让当前视图以最适合的尺寸来调整大小,参照物是父视图尺寸,没有父视图就用屏幕bounds。
    你不可以直接对这个方法进行重写,如果你需要改变视图的默认尺寸,你可以重写- (CGSize)sizeThatFits:(CGSize)size这个方法。在这个方法里面可以进行一些必要的计算并在这个方法中对计算的结果进行返回。

    • - (CGSize)sizeThatFits:(CGSize)size

    要求视图去计算和返回一个最适合的指定尺寸的尺寸。
    传入:是一个指定的最适合的尺寸
    返回:是view的子view最适合的尺寸
    默认实现是返回view自己的size
    例如:

    • UISwitch 返回一个固定尺寸的值去显示一个标准的切换视图。
    • UIimageview 会返回当前显示image的尺寸。
    应用场景
    • 对navigationitem 的设置
    • 对uibarbuttonitem 的设置
    • UIlable
    • imageview

    使用注意点:

    • sizeToFit不应该再子类中被重写,应该重写sizeThatFitssizeToFit会自动调用sizeThatFits方法
    • sizeThatFits传入的参数是receiver当前的size,返回一个合适的size
    • sizeToFit可以被手动直接调用,sizeToFitsizeThatFits方法都没有递归,对subviews不负责,只负责自己。
    • sizeThatFits 不会改变receiver的size,sizeToFit会改变receiver的size
    view的绘制渲染机制和runloop有什么关系

    堆栈信息:


    image.png
    底层实现

    在操作UI时,比如改变了frame,更新了UIView/CALayer的层次时,或者手动调用了UIView/CALayer的setNeedsLayout/setNeedsdisplay 方法后,这个UIView/CALayer就被标记为待处理,并被提交到一个全局的容器中去。苹果注册了一个Observer 来监听runloop的BeforeWaiting(即将进入休眠)和Exit(即将退出)事件,回调一个函数:
    _ZN2CA11Transaction17observer_callbackEP19__CFRunLoopObservermPv(),这个函数会遍历所有待处理的UIView/CAlayer,以执行实际的绘制和调整,并更新UI界面。

    列表卡顿,到底是什么引起的

    iOS的mainrunloop 是一个60fps(每秒刷新60次)的回调,也就是说每16.7ms 会绘制一次屏幕,这个时间段内要完成view的缓冲区创建,view内容的绘制(如果重写了drawRect),这些CPU的工作。然后将这个缓冲区交给GPU渲染,这个过程又包括多个view的拼接(compositing)和纹理的渲染(Texture)等,最终显示在屏幕上。因此,如果16.7ms 内完不成这些操作,比如CPU做了太多的工作,或者view层次过于多,图片过于大,导致GPU压力太大,就会导致’卡‘的显现,也就是丢帧。

    我们经常在drawRect方法里绘制代码,该方法是谁调用的 何时调用的?
    • 在[ZYYView drawRect:]方法之前,调用了[CALayer drawInContext:] 和 [UIView(CALayerDelegate) drawLayer:inContext:],这几个方法是在 addSubView:方法之后 才会调用的,所以drawRect方法是在addSubView:函数触发的。
      image.png
    drawrect方法内为何第一行代码总要获取图形的上下文

    每一个UIView都有一个layer,每一个layer都有个content,这个content指向的是一块缓存,叫做backing store,当UIView被绘制时,CPU执行drawRect,通过context 将数据写入backing store,当backing store写完后,通过render server 交给GPU去渲染,将backing store中的bitmap数据显示在屏幕上,所以在drawRect方法中,要首先获取context。

    image.png
    视图绘制过程
    • 创建自定义视图,初始化坐标,确认是否设置了frame
    • 没有设置frame 就不会调用 drawRect
    • 设置frame,调用addsubview 方法添加视图
    • 将此view的layer(在初始化的时候就已创建)的delegate 设置成此view
    • 首先CPU 会为layer 分配一块内存来绘制bitmap,叫做backing store,然后layer创建指向这块bitmap缓冲区的指针,叫做CGContextRef
    • 调用此view的layer的 drawInContext:方法
    • 如果代理实现了drawLayer:inContext: 方法,调用代理的 drawLayer:inContext:方法,也就是view的 drawLayer:inContext:方法
    • 执行drawRect方法
    • 通过CoreGfraphic 的API,也叫做Quartz2D,绘制bitmap。
    • 将layer的content 指向生成的bitmap
      到此CPU的工作已经完成,剩下的就是GPU的工作。
    view的渲染机制 和GPU之间的关系

    GPU处理的单位是texture
    基本上我们控制GPU都是通过open GL来完成,但是从bitmap到texture之间需要一座桥梁,core animation正好充当了这个角色,Core Animation对open GL 的api 有一层封装,当我们的要渲染的layer已经有了bitmap content的时候,这个content一般来说是一个CGImageRef,CoreAnimation会创建一个open GL的texture,并将CGImageRef(bitmap)和这个texture绑定,通过textureID来标识。
    这个对应关系建立起来之后,剩下的任务就是GPU如何将texture渲染到屏幕上了。

    从硬件的角度来看

    GPU的工作模式:CPU将准备好的bitmap放到ram里,GPU去搬这块内存到VRAm(显存的一种)中处理。而这个过程GPU所能承受的极限大概在16.7ms完成一帧的处理。所以最开始提到的60fps其实就是GPU能处理的最高频率。

    总结视图的渲染过程
    • 创建视图,初始化过程(内部创建layer)
    • 将视图添加到父视图,调用addSubViews 方法
    • 添加后,将layer的delegate 赋值为此view
    • CPU 创建一块内存缓冲区,名为backing store,用来盛放绘制的bitmap 数据。
    • layer 会创建一个指向这个内存的指针,CGContextRef 类型(也就是当前的上下文对象)
    • 调用layer的drawInContext: 方法
    • 调用layer delegate的 drawLayer:Incontext:方法
    • 调用 drawRect 方法
    • 以上三个方法将绘制的内容(bitmap)送到backing store
    • 将绘制好的bitmap 给layer的content ,到此 CPU工作完成
    • GPU将ram 中的bitmap(使用open GL api)渲染到vram 中。
      参考链接
      https://blog.csdn.net/yangyangzhang1990/article/details/52452707

    相关文章

      网友评论

        本文标题:UIView 的渲染过程

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