UITableView
1、重用机制:先从重用池里取
2、多线程下数据源同步(删除(主线程)后又加载数据(子线程)):
解决方案:并发访问数据拷贝和串行访问
3、UIView和CALayer的关系和区别:UIView有属性layer,layer指向了CALayer类型的变量,然后实际上backgroundColor是对CALayer同名属性方法的包装,实际上UIView的显示部分是由CALayer的contents来决定的,contents对应的叫做backing store ,它是一个bit map类型的位图,我们显示的UIView控件可以理解为都是位图。
即UIView和CALayer的区别是:UIView为CALayer提供显示的内容,以及负责处理触摸等事件,参与响应链,而CALayer负责显示内容contents
为什么UIView只负责事件传递和视图响应链,而显示部分的工作都由CALayer来做呢?(考查的是设计原则上的理解)
这体现了系统设计UIView和CALayer时所使用的设计原则,就是单一职责原则
4、事件传递和视图响应链:
事件传递主要和两个方法有关:
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event;
用途:哪个视图响应事件就把哪个视图返回 - (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event;
用来判断某一个点击的位置是否在当前视图内,在的话就返回YES
事件传递的流程:假如我们点击了屏幕的某一个位置- 传递UIApplication--window--hitTest--pointInside--subviews来查找最终响应的视图(UIView1,UIView2,UIView3),并且以倒序的形式遍历,最后添加的window的UIView最先被遍历到,每一个UIView都会去调用它们的hitTest方法,hitTest再调用PointInside,每一个view又会遍历它的子视图,在调用pointInside之前会先判断当前view是否是userInteractiveEnabled或者hidden或者alpla值小于0.01,如果是的话直接返回NO。如果是Yes,则会调用pointInside方法,找到当前这个视图以后,这个视图会继续遍历它自己的子视图,子视图继续刚才的两个步骤,目的是找到最合适的视图,如果这个视图所有的子视图均返回nil,则这个视图就为最合适的view。
5、响应者链
如果第一响应者不处理,则传给它的父视图,如果父视图不是ViewController的view则继续向上传一直会给控制器的view,然后传给ViewController-window-UIApplication-applicationDelegate,视图响应相关的方法有:touchbegin,touchMove,touchEnd,如果最后传到delegate依然没有处理这个事件,则这个事件就会被废弃掉。
6、图像显示原理
CPU和GPU均是通过总线连接起来的,在CPU中计算的结果往往是一个位图,在通过总线传给GPU,GPU拿到位图以后会做图层的渲染和纹理的合成,之后会把结果放到帧缓冲区中,根据视频控制器根据vsync信号在指定时间之前去提取帧缓冲区的显示内容,最终显示到手机屏幕中。
我们来看CPU和GPU分别做了哪些事:
当创建一个UIView后,显示部分是由CALayer来负责的,CALayer里有一个Content属性,就是我们最终要绘制到屏幕上的一个位图,比如我们要创建一个UILabel,contents里最终要放置的就是"Hello world"的一个文字位图,然后系统会在合适的时候回调给我们一个drawRect的方法,在drawRect我们可以绘制自己想要绘制的内容,绘制好的内容会经由Core Animation提交给GPU部分的Open GL渲染管线,进行最终的渲染和纹理的合成,然后显示到屏幕上,这就是视图显示到屏幕上的一个大致过程,包括coreAnimation和之前的是发生在CPU上的,而OpenGL是发生在GPU上的。
再看CPU和GPU在具体的工作上又有哪些承担:
CPU要完成UI布局(layout,包括frame设置,size计算)和显示(display,即绘制过程,如drawRect就发生在这一步),之后会做一些准备(比如要显示UIImageView,这个image往往是不能直接显示到屏幕上去的,往往需要对图片进行编解码,编解码则发生在这一步中)工作,然后会把位图(由CoreAnimation针对结果进行提交)提交到GPU上。
GPU渲染管线,即OpenGL的渲染管线,的工作有:顶点着色(对位图进行处理)- 图元装配 - 光栅化 - 片段着色 - 片段处理,这五个步骤做完之后,则将像素点提交给帧缓冲区,则视屏控制器在vsync信号到来之前去帧缓冲区提取要显示到屏幕上的内容,这些就构成了OpenGL渲染管线的相关内容。
7、基于图像显示原理的相关面试问题:UI卡顿,掉帧的原因
页面滑动的流畅性是60FPS,即每秒会有60次的画面更新,即每隔16.7毫秒就要产生一帧画面,在这16.7毫秒之内要由CPU和GPU协同产生一帧的数据,比如CPU花费一定的时间做UI布局,文本的计算以及图片的解码,然后最终把产生的位图提交给GPU,再由GPU产生图层的合成纹理渲染,然后再准备把下一帧画面,在下一帧的VSync信号到来之前就可以显示画面,假如CPU在做工作的时候花费的时间特别长的话留给GPU的时间就非常少,GPU要完成图层的合成纹理渲染,CPU和GPU花费的总时间就要超过16.7ms,这样在下一帧VSync信号到来的时候,我们没有准备好当下的这一帧的画面,这样就会产生掉帧,我们看到的画面的效果就是卡顿,这样就可以解释UI卡顿和掉帧的原因,总结一下就是在规定的16.7ms之内,在下一帧VSync信号到来之前,CPU和GPU并没有完成下一帧画面的合成,这样就会形成卡顿或者掉帧。
根据上面我们可以提高UITableView的页面滑动方案:
面试官问基于tableView或UIStackView都有哪些优化方案,你又是怎么做的?
可以通过减轻CPU的时长和压力来达到优化的效果,如1、对象的创建,调整和销毁可以放到子线程中去做,可以减少CPU的时间,2、视图的预排版(布局计算,文本计算)可以放在子线程中去做,这样主线程就可以有更多的时间来响应用户的交互,3、预渲染(文本的异步绘制、图片编解码等)
GPU的优化有:1、有纹理渲染,比如我们触发了离屏渲染的话,如圆角、maskToBounds、阴影和蒙层都会触发GPU的离屏渲染,这样GPU做纹理渲染的工作量就会非常大,2、视图混合,比如我们视图层级非常复杂的话,GPU就要做每一个视图的合成,做每一个像素点的像素值,需要做大量的运算,所以减少视图层级也可以减少GPU的压力。
8、UIView的绘制原理相关的面试问题
UIView的绘制步骤:当调用[UIView setNeedsDisplay]之后,实际上并没有发生当前UIView的绘制工作,而是之后的某个时间点才会真正进行UIView的绘制工作,为什么呢?
当调用[UIView setNeedsDisplay]后,会立即触发[UIView.layer setNeedsDisplay],相当于在layer上打了一个脏标记,等到当前的RunLoop将要结束的时候才会调用[CALayer display],然后进入到视图真正的绘制工作当中,[CALayer display]的内部实现中,首先会判断当前layer的delegate是否实现了displayLayer:方法,如果没有,则会进入到系统绘制流程中,如果为YES,则会提供异步绘制的入口,为异步绘制留有了余地,这就构成了UIView的绘制原理的过程。
系统的绘制流程:CALayer内部创建backing store(CGContextRef,这个Ref可以在drawRect里取到),然后layer会判断是否有Delegate,如果没有,则会调用[CALayer drawInRect:],如果有代理,则调用[layer.delegate drawLayer: InContext:]这是发生在系统内部当中的,然后在合适的时机给我们一个回调方法,即drawRect方法,最后由Layer上传backing store到GPU,就结束了系统的绘制流程。
如何实现异步绘制?
面试官问是否知道异步绘制,我们又应该怎么做异步绘制,实际上就是基于系统给我们提供的口子,layer.delegate如果遵从了displayLayer:方法了,就可以进入到异步绘制的流程中,则需要代理负责生成对应的bitmap,设置该bitmap作为layer.contents属性的值。
时序图:主队列中调用[View setNeesDisplay]后,当前RunLoop快要结束的时候,系统调用[对应视图的layer display],如果代理实现了[view displayLayer:]后则会调用这个方法,然后会调用子线程的切换,在子线程中做位图的绘制(-CGBitMapContextCreate()创建位图,-CoreGraphic API -CGBitMapContextCreateImage()生成图片),然后回到主队列,提交给Layer的contents属性,这样就完成的UI控件的异步绘制过程。
9、离屏渲染(针对GPU)
在屏渲染,指的是GPU的渲染操作是在当前用于显示的屏幕缓冲区中进行,离屏渲染指的是GPU在当前屏幕缓冲区以外新开辟一个缓冲区进行渲染操作,也就是当我们设置某一些UI图层的图层属性在未合成之前不能直接用于显示的时候就会触发离屏渲染,如圆角,蒙层,可以这样回答离屏渲染:当我们指定了某些图层的属性,标记为在未合成之前不能用于当前屏幕上直接显示的时候就会触发离屏渲染,离屏渲染是在GPU的层面。
何时会触发离屏渲染?
圆角(和MaskToBounds为YES时一起使用),图层蒙版,阴影,光栅化都会触发。
为何要避免离屏渲染?
离屏渲染触发了GPU的多通道渲染管线,产生了额外的开销,所以要避免离屏渲染,高级工作师面试的可以这样回答,离屏渲染增加了GPU的工作量,这很有可能导致CPU和GPU的加起来的工作耗时加起来超过了16.7ms,有可能出现卡顿和掉帧,所以我们要避免离屏渲染,初级和中级可以这样回答,离屏渲染创建了新的渲染缓冲区,增加了内存上的开销,包括多通道渲染管线的合成,就需要上下文切换,就有CPU的额外开销