一、UIView的绘制流程
UIView是如何到显示的屏幕上的。
这件事要从RunLoop开始,RunLoop是一个60fps的回调,也就是说每16.7ms绘制一次屏幕,也就是我们需要在这个时间内完成view的缓冲区创建,view内容的绘制这些是CPU的工作;然后把缓冲区交给GPU渲染,这里包括了多个View的拼接(Compositing),纹理的渲染(Texture)等等,最后Display到屏幕上。但是如果你在16.7ms内做的事情太多,导致CPU,GPU无法在指定时间内完成指定的工作,那么就会出现卡顿现象,也就是丢帧。
![](https://img.haomeiwen.com/i11321767/8b7d4aaa16575031.png)
1.当调用UIView的setNeedsDisplay后系统会立刻调用view的layer的同名方法[view.layer setNeedsDisplay],之后相当于在layer上面打上了一个脏标记
2.然后再当前runloop将要结束的时候,才会调用CALayer的display函数方法,然后才进入到当前视图的真正绘制工作的流程当中
3.CALayer的display方法,在内部会首先判断layer的delegate是否响应displayLayer这个方法
若不响应,则系统开始绘制流程
若响应,则开始异步绘制 (主要讲系统绘制流程,异步后续学习后会补充)
二、系统绘制流程
![](https://img.haomeiwen.com/i11321767/bbffd89670525595.png)
1.首先CALayer内部会创建一个CGContextRef,在drawRect方法中,可以通过上下文堆栈当中的取出这个context,拿到的就是当前视图的上下文或者说是backing store
- (void)drawRect:(CGRect)rect{
//获取上下文
CGContextRef ctx = UIGraphicsGetCurrentContext();
}
2.判断layer是否有delegate:
2.1如果有delegate,则会执行[layer.delegate drawLayer:inContext](这个方法的执行是在系统内部执行的),然后在这个方法中会调用view的drawRect:方法,也就是我们重写view的drawRect:方法才会被调用到。
2.2如果没有delegate,会调用layer的drawInContext方法,也就是我们可以重写的layer的该方法,此刻会被调用到。
3.最后把绘制完的backing store(可以理解为位图)提交给GPU。
什么情况会调用draw rect方法
1、controller的loadView、viewdidLoad方法调用之后,view即将出现在屏幕之前系统调用drawRect。
2、sizeToFit方法调用之后。
3、设置contetMode为UIViewCOntentModelRedraw,之后每次更改frame的时候调用redraw方法。
4、调用setNeedsDisplay方法。
就以上流程来说,我们开发时一般也只是重写drawRect方法来实现自定义视图,而在阅读代码后,发现航班动态并未重写drawRect方法更别说再其中进行耗时操作了。那么到这好像并没有发现航班动态卡顿的终极原因。继续查找资料发现,以上的drawRect,setNeedsDisplay等方法都是绘制方法,与之相关的还有布局方法。
视图布局相关方法:
1、-(void)layoutSubviews;
对subview重新布局
2、-(void)setNeedsLayout;
将视图标记为需要重新布局,这个方法会在系统runloop的下一个周期自动调用layoutSubviews。
3、-(void)layoutIfNeeded;
如果有需要刷新的标记,立即调用layoutSubviews进行布局
在没有外界干预的情况下,一个view的frame或者bounds发生变化时,系统会先去标记flag这个view,等下一次渲染时机到来时(也就是runloop的下一次循环),会去按照最新的布局去重新布局视图。
setNeedLayout就是给这个view添加一个标记,告诉系统下一次渲染时机需要重新布局这个视图。
layoutIfNeed就是告诉系统,如果已经设置了flag,那不用等待下个渲染时机到来,立即重新渲染。前提是设置了flag。
而layoutSubviews则是由系统去调用,不需要我们主动调用,我们只需要调用layoutIfNeed,告诉系统是否立即执行重新布局的操作。
layoutSubviews调用时机
1、init初始化不会触发layoutSubviews。
2、addSubview会触发layoutSubviews。
3、设置view的Frame会触发layoutSubviews,(前提是frame的值设置前后发生了变化。)
4、滚动一个UIScrollView会触发layoutSubviews。
5、旋转屏幕会触发父UIView上的layoutSubviews事件。
6、改变一个UIView大小的时候也会触发父UIView上的layoutSubviews事件。
7、直接调用setLayoutSubviews。(不建议这么做)
其次在学习过程中还了解到viewController的loadView方法的一些原理 。loadView方法负责创建UIViewController的view,每次访问UIViewController的view,比如controller.view、self.view,且view为nil,就会调用loadView方法。
![](https://img.haomeiwen.com/i11321767/0ad7a4c73c33934b.png)
关于[super loadView]中的默认实现:(1)首先去查找与UIViewController相关联的xib文件,通过加载xib文件来创建UIViewController的view;(2)如果没有找到关联的xib文件,就会创建一个空白的UIView,然后赋值给UIViewController的view属性。
在项目中大量viewController都重写了loadView方法,并在其中创建view并赋值给self.view。
![](https://img.haomeiwen.com/i11321767/aa7bdce7df72eee8.png)
重点在红框中,大致的意思是我们可以在此方法中直接创建view并赋值给viewController的view属性,但如果我们这么做了,就不用再调用super方法了。这样能节省一点开销。
网友评论