当前位置: 首页 > 科技观察

浅谈iOS页面流畅度技巧

时间:2023-03-19 16:28:48 科技观察

1.屏幕显示图像原理首先明确两个概念:水平同步信号和垂直同步信号。CRT电子枪按照上图的方法从上到下逐行扫描。扫描完成后,显示屏会出现一帧画面,然后电子枪回到初始位置继续下一次扫描。当电子枪切换到新的行扫描时,显示器会发出水平同步信号(Horizo??nalSynchronization),简称HSync;一帧画完后,电子枪会回到原来的位置,显示器会发出垂直同步信号(VerticalSynchronizationSignal),简称VSync。CUP计算并提交显示内容给GPU。GPU渲染完成后,将渲染结果放入帧缓冲区,然后视频控制器根据VSync信号逐行读取帧缓冲区中的数据,最后经过各种数模转换。到显示器。2.卡顿的原因如果CPU或GPU在一个VSync时间内没有完成内容提交,则该帧会被丢弃,等待下一次显示,而显示会保持之前的内容不变。这就是卡顿的原因。三、占用CPU资源的原因及解决办法1、对象的创建对象的创建会分配内存,调整属性,甚至读取文件,都比较消耗CPU资源。因此,你可以:(1)尽量使用轻量级的对象,而不是重的对象,比如CALayer比UIView要轻很多,在不需要响应触摸事件的时候使用CALayer来显示更合适;(2)如果对象不涉及UI操作,尽量放在后台线程中创建;(3)通过storyboard创建视图对象时,其资源消耗会比直接通过代码创建对象大很多,尽量避免使用;(4)尽量延迟创建对象的时间,把对象的创建分散到多个任务中;(5)如果对象可以被重用,并且重用的成本低于释放和创建新对象,那么这些对象应该尽可能在缓冲池中被重用。对象大小调整对象大小调整也是经常消耗CPU资源的地方。特别是CALayer:(1)CALayer没有内部属性。当调用一个属性方法时,它在运行时通过resolveInstanceMethod临时给对象添加一个方法,并将相应的属性值保存在一个内部Dictionary中,同时还要通知delegate,创建动画等,消耗大量资源;(2)UIView的显示相关属性(如frame/bouds/transform等)实际上是从CALayer属性映射过来的,所以在调整UIView的这些属性时,消耗的资源要比一般属性大很多,所以类似的不必要的属性修改应最小化;(3)视图层级调整时,UIView和CALayer之间会有很多调用和通知,所以在优化性能的时候,尽量避免调整视图层级,添加和移除视图。3.对象销毁当容器类持有大量对象时,销毁时的资源消耗非常明显。因此,尽量在后台线程中释放该对象。你可以这样做:将对象捕获到一个块中,然后将其扔到后台队列中发送消息以避免编译警告,这样就可以在后台线程中销毁该对象:NSArray*tmp=self.arr_data;self.arr_data=无;dispatch_async(queue,^{[tmpclass];});4.对象布局在后台线程中提前计算好,视图的布局被缓存起来。不管用什么技术来布局view,最终都会落在UIView.frame/bounds/center等属性的调整上5.Autolayout是Apple自己提倡的技术,在大多数情况下可以提高开发效率,但通常会为复杂的视图带来严重的性能问题。随着视图数量的增加,Autolayout带来的CPU消耗会呈指数增长。6.文本计算如果一个界面包含大量的文本,文本的宽高计算会占用很大一部分资源,这是无法避免的。7、文本渲染所有屏幕上可以看到的文本内容控件,包括UIWebView,都是通过CoreText在底层以Bitmaps的形式进行排版和绘制,排版和绘制都在主线程中进行。显示大量文字时,CPU的压力非常大。可以使用TextKit或者一级CoreText,通过自定义文本控件异步绘制文本。虽然麻烦,但是有强大的优势:(1)CoreText对象可以直接获取文本的宽高等信息,避免了多次计算(UILabel调整大小时计算一次,UILabel绘制时内部计算一次));(2)CoreText对象占用内存少,可以缓存起来供以后多次渲染使用。8.图片解码当使用UIImage或CGImageSource的方法创建图片时,图片数据不会立即被解码。图像设置为UIImageView或CALayer.contents,CGImage中的数据将在CALayer被带到GPU之前被解码。这一步发生在主线程上,是不可避免的。如果想绕过这种机制,常用的方法是先在获取的线程中将图片绘制到CGBitmapContext中,然后直接从Bitmap中创建图片。目前常见的网络图片库都有这个功能。9.图像绘制指用CG开头的方法将图像绘制到画布中,然后从画布中创建并显示图片。常见的是[UIViewdrawRect:]。CoreGraphic方法通常是线程安全的,因此图形的绘制可以在后台线程上运行。如下:(实际情况比这个复杂,但是原理基本一样)-(void)display{dispatch_async(backgroundQueue,^{CGContextRefctx=CGBitmapContextCreate(...);//drawincontext...CGImageRefimg=CGBitmapContextCreateImage(ctx);CFRelease(ctx);dispatch_async(mainQueue,^{layer.contents=img;});});}4.GPU资源消耗的原因及解决办法GPU可以做比较简单的事情:接受提交的纹理(Texture))和顶点描述(三角形),应用变换、混合和渲染,然后输出到屏幕。你看到的通常主要是两种纹理(图片)和形状(三角模拟的矢量图形)。1.Texture渲染所有的Bitmaps,包括图片、文字、光栅化内容,最终都是从内存提交到显存,绑定为GPUTexture。无论是提交到显存的过程,还是GPU调整渲染Texture的过程,都会消耗大量的GPU资源。当短时间内显示大量图片时(比如TableView),CPU占用率很低,GPU占用率很高,界面会掉帧。当图片过大,超过了GPU的最大纹理尺寸时,图片需要先经过CPU预处理,这会给CPU和GPU都带来额外的消耗。2.视图的合成当多个视图(或CALayer)重叠显示在一起时,GPU会先将它们混合在一起。如果视图结构过于复杂,混合过程也会消耗大量的GPU资源。因此,应尽可能减少视图的数量和层次,并在不透明视图中标记不透明属性,以避免无用的alpha通道合成。也可以将多个视图预渲染成一张图片来显示3.图形生成CALayer的边框、圆角、阴影、遮罩(mask),CASharpLayer的矢量图形显示,通常会触发离屏渲染(offscreenrendering),而off-屏幕渲染通常发生在GPU上。当列表中出现大量圆角的CALayer并快速滑动时,GPU资源可能快满了,而CPU资源消耗很小。此时界面仍然可以正常滑动,但平均帧率下降到很低的水平。这时候可以尝试开启CALayer.shouldRaster属性,但这会将离屏渲染操作转移到CPU上。对于一些只需要圆角的场合,可以使用绘制的圆角图片叠加在原视图上,模拟出相同的视觉效果。最彻底的办法:在后台线程中绘制需要显示为图片的图形,避免使用圆角、阴影、遮罩等属性。