iOS 页面流畅技巧(1)
一、屏幕显示图像原理
首先明确两个概念:水平同步信号、垂直同步信号。
CRT 的电子枪按照上图中的方式,从上到下一行一行的扫描,扫描完成后显示器就呈现一帧画面,随后电子枪回到初始位置继续下一次的扫描。当电子枪切换到新的一行准备扫描时,显示器会发送一个水平同步信号(Horizonal Synchronization),简称HSync;完成一帧画面绘制后,电子枪会回到原位,显示器会发送一个垂直同步信号(Vertical Synchronization),简称VSync。
CUP 计算好显示内容提交到 GPU,GPU 渲染完成后将渲染结果放入帧缓冲区,之后视频控制器按照 VSync 信号逐行读取帧缓冲区中的数据,最后经过各种数模转换传递给显示器显示。
二、卡顿产生的原因
如果在一个 VSync 时间内,CPU 或者 GPU 没有完成内容提交,则那一帧就会被丢弃,等待下一次再显示,而这时显示屏会保留之前的内容不变,这就是卡顿的原因。
三、CPU 资源消耗的原因和解决方案
3.1 对象的创建
对象的创建会分配内存、调整属性、甚至还有读取文件的操作,比较消耗 CPU 资源。因此可以:
①、尽量用轻量的对象代替重量的对象。如 CALayer 比 UIView 轻量的多,在不需要响应触摸事件时,用 CALayer 显示更合适;
②、如果对象不涉及 UI 操作,尽量放到后台线程去创建;
③、通过 storyboard 创建视图对象时,其资源消耗会比直接通过代码创建对象要大非常多,所以尽量避免使用;
④、尽量推迟对象创建的时间,并把对象的创建分散到多个任务中去;
⑤、如果对象可以复用,并且复用的代价比释放、创建新对象要小,那么这类对象应当尽量放到一个缓存池里复用。
3.2 对象调整
对象的调整也是经常消耗 CPU 资源的地方。尤其是 CALayer:
①、CALayer 内部没有属性,当调用属性方法时,它内部是通过运行时 resolveInstanceMethod 为对象临时添加一个方法,并把对应属性值保存到内部的一个 Dictionary 中,同时还会告知 delegate、创建动画等,非常消耗资源;
②、UIView 关于显示相关的属性(比如 frame/bouds/transform 等)实际上都是 CALayer 属性映射出来的,所以对UIView 的这些属性进行调整时,消耗的资源要远大于一般的属性,因此应该尽量减少类似的不必要的属性的修改;
③、当视图层次调整时,UIView、CALayer 之间会出现很多调用与通知,所以在优化性能时,应该尽量避免调整视图层次、添加和移除视图。
3.3 对象销毁
当容器类持有大量对象时,其销毁时的资源消耗就非常明显。所以,尽量去后台线程释放对象。可以这么做:把对象捕获到 block 中,然后扔到后台队列去随便发送个消息以避免编译警告,就可以让对象在后台线程销毁了:
NSArray * tmp = self.arr_data;
self.arr_data = nil;
dispatch_async(queue, ^{
[tmp class];
});
3.4 对象布局
在后台线程提前计算好视图布局、并对视图的布局进行缓存。
不论通过何种技术对视图进行布局,最终都会落到对 UIView.frame/bounds/center 等属性的调整上。
3.5 Autolayout
这是苹果本身提倡的技术,在大部分情况下能很好的提升开发效率,但对于复杂视图来说常会产生严重的性能问题。随着视图数量的增长,Autolayout 带来的 CPU 消耗会呈指数级增长。
3.6 文本计算
如果一个界面中包含大量的文本,文本的宽高计算会占用很大一部分资源,并且不可避免。
3.7 文本渲染
屏幕上能看到的所有的文本内容控件包括 UIWebView,在底层都是通过 CoreText 排版、绘制为 Bitmap 显示的,并且该排版、绘制都是在主线程进行的。
显示大量文本时,CPU 的压力非常大,可以通过自定义文本控件,用 TextKit 或最底层的 CoreText 对文本异步绘制,尽管麻烦但优势强大:
①、CoreText 对象能直接获取文本的宽高等信息,避免了多次计算(调整 UILabel 大小时算一遍、UILabel 绘制时内部再算一遍);
②、CoreText 对象占用内存较小,可以缓存下来以备稍后多次渲染。
3.8 图片解码
用 UIImage 或者 CGImageSource 的方法创建图片时,图片数据并不会立刻解码。图片设置到 UIImageView 或者 CALayer.contents 中,并且 CALayer 被提到 GPU 前,CGImage 中的数据才会得到解码。
解码过程是一个相当复杂的任务,需要消耗非常长的时间。解码后的图片将同样使用相当大的内存。
该步是发生在主线程,并且不可避免。如果想绕开这个机制,常见的方法是在后台线程先把图片绘制到 CGBitmapContext 中,然后从 Bitmap 直接创建图片。目前常见的网络图片库都自带这个功能。
用于加载的 CPU 时间相对于解码来说根据图片格式而不同。对于 PNG 图片来说,加载会比 JPEG 更长,因为文件可能更大,但是解码会相对较快,而且 Xcode 会把 PNG 图片进行解码优化之后引入工程。JPEG 图片更小,加载更快,但是解压的步骤要消耗更长的时间,因为 JPEG 解压算法比基于 zip 的 PNG 算法更加复杂。
3.9 图像的绘制
是指用那些以 CG 开头的方法把图像绘制到画布中,然后从画布创建图片并显示。常见的就是 [UIView drawRect: ]。CoreGraphic 方法通常是线程安全的,所以图像的绘制可以放到后台线程运行。如下:(实际情况比这个复杂,但原理基本一致)
- (void)display
{
dispatch_async(backgroundQueue, ^{
CGContextRef ctx = CGBitmapContextCreate(...);
// draw in context...
CGImageRef img = CGBitmapContextCreateImage(ctx);
CFRelease(ctx);
dispatch_async(mainQueue, ^{
layer.contents = img;
});
});
}
四、GPU 资源消耗原因和解决方案
GPU 能干的事情比较单一:接受提交的纹理(Texture)和顶点描述(三角形)、应用变换(transform)、混合并渲染,然后输出到屏幕上。看到的内容通常主要是纹理(图片)和形状(三角模拟的矢量图形)两类。
4.1 纹理的渲染
所有的 Bitmap,包括图片、文字、栅格化的内容,最终都要由内存提交到显存,绑定为 GPU Texture。不论是提交到显存的过程,还是 GPU 调整和渲染 Texture 的过程,都要消耗不少 GPU 资源。
当在短时间内显示大量图片时(如 TableView),CPU 占用率很低,GPU 占用非常高,界面会掉帧。
当图片过大,超过 GPU 的最大纹理尺寸时,图片需要先由 CPU 进行预处理,这对 CPU 跟 GPU 都会带来额外的消耗。
4.2 视图的混合(Composing)
当多个视图(或者 CALayer)重叠在一起显示时,GPU 会首先把他们混合到一起。如果视图结构过于复杂,混合的过程也会消耗很多的 GPU 资源。
所以应当尽量减少视图数量和层次,并在不透明的视图里标明 opaque 属性以避免无用的 Alpha 通道合成。
也可以把多个视图预先渲染为一张图片来显示。
4.3 图形的生成
CALayer 的 border、圆角、阴影、遮罩(mask),CASharpLayer 的矢量图形显示,通常会触发离屏渲染,而离屏渲染通常发生在 GPU 中。
当列表中出现大量圆角的 CALayer 并且快速滑动时,GPU 资源可能几近占满,而 CPU 资源消耗很少,这时候界面仍能正常滑动但平均帧数降到很低。这时候可以尝试开启 CALayer.shouldRaster 属性,但这会离屏渲染操作转嫁到 CPU 上。
对于只需要圆角的某些场合,可以用一张已经绘制好的圆角图片覆盖到原视图上来模拟出相同的视觉效果。
最彻底的做法:把需要显示的图形在后台线程绘制为图片,避免使用圆角、阴影、遮罩等属性。
五、文章
iOS 页面流畅技巧(1)的更多相关文章
- iOS 页面流畅技巧(2)
一.屏幕显示图像的原理 首先从过去的 CRT 显示器原理说起.CRT 的电子枪按照上面方式,从上到下一行行扫描,扫描完成后显示器就呈现一帧画面,随后电子枪回到初始位置继续下一次扫描.为了把显示器的显示 ...
- iOS界面流畅技巧之微博 Demo 性能优化技巧
微博 Demo 性能优化技巧 我为了演示 YYKit 的功能,实现了微博和 Twitter 的 Demo,并为它们做了不少性能优化,下面就是优化时用到的一些技巧. 预排版 当获取到 API JSON ...
- iOS开发实用技巧—项目新特性页面的处理
iOS开发实用技巧篇—项目新特性页面的处理 说明:本文主要说明在项目开发中会涉及到的最最简单的新特性界面(实用UIScrollView展示多张图片的轮播)的处理. 代码示例: 新建一个专门的处理新特性 ...
- iOS:小技巧(不断更新)
记录下一些不常用技巧,以防忘记,复制用. 1.获取当前的View在Window的frame: UIWindow * window=[[[UIApplication sharedApplication] ...
- iOS开发实用技巧—在手机浏览器头部弹出app应用下载提示
iOS开发实用技巧—在手机浏览器头部弹出app应用下载提示 本文介绍其简单使用: 第一步:在本地建立一个访问的服务端. 打开本地终端,在本地新建一个文件夹,在该文件夹中存放测试的html页面. ...
- [转] iOS性能优化技巧
(转自:hhttp://www.raywenderlich.com/31166/25-ios-app-performance-tips-tricks#arc, http://blog.ibireme. ...
- iOS:小技巧(19-02-12更)
记录下一些不常用技巧,以防忘记,复制用. 1.UIImageView 和UILabel 等一些控件,需要加这句才能成功setCorn _myLabel.layer.masksToBounds = YE ...
- iOS页面传值-wang
iOS页面间传值的方式(NSUserDefault/Delegate/NSNotification/Block/单例) 实现了以下iOS页面间传值:1.委托delegate方式:2.通知notific ...
- iOS页面间传值的方式(Delegate/NSNotification/Block/NSUserDefault/单例)
iOS页面间传值实现方法:1.通过设置属性,实现页面间传值:2.委托delegate方式:3.通知notification方式:4.block方式:5.UserDefault或者文件方式:6.单例模式 ...
随机推荐
- 大厂面试题:集群部署时的分布式 session 如何实现?
面试官心理分析 面试官问了你一堆 dubbo 是怎么玩儿的,你会玩儿 dubbo 就可以把单块系统弄成分布式系统,然后分布式之后接踵而来的就是一堆问题,最大的问题就是分布式事务.接口幂等性.分布式锁, ...
- Windows 使用激活服务器激活操作步骤
最近装了win10企业版系统,总结下激活步骤,激活后是正版,半年后需要重新激活,不介意的小伙伴可以试试,这不是重点,重点是企业版超级clean...... 服务器激活系统步骤,打开cmd或者xshel ...
- mysql 存储过程 执行存储过程修改了表中所有行的信息
存储过程中的where条件语句,如果传入的参数和表字段名相同,存储过程就会把这个约束条件忽略.小结:存储过程中传递的参数名不要和字段名相同.特别是修改.删除等操作,可能会对整张表产生影响.后果会很严重 ...
- PHP 深度理解preg_quote()函数
php手册上说,preg_quote()函数的作用是转义正则表达式字符.那么下面我们来深入了解下这个函数是怎么使用的: 说明:preg_quote()函数常和preg_replace()函数一起使用. ...
- VUE实现Studio管理后台(九):开关(Switch)控件,输入框input系列
接下来几篇作文,会介绍用到的输入框系列,今天会介绍组普通的调用方式,因为RXEditor要求复杂的输入功能,后面的例子会用VUE的component动态调用,就没有今天的这么直观了,控件的实现原理都一 ...
- python——字符串截取
str = ‘0123456789’ print str[0:3] #截取第一位到第三位的字符 print str[:] #截取字符串的全部字符 print str[6:] #截取第七个字符到结尾 p ...
- SSM动态切换数据源
有需求就要想办法解决,最近参与的项目其涉及的三个数据表分别在三台不同的服务器上,这就有点突兀了,第一次遇到这种情况,可这难不倒笔者,资料一查,代码一打,回头看看源码,万事大吉 1. 预备知识 这里默认 ...
- SpringBoot图文教程15—项目异常怎么办?「跳转404错误页面」「全局异常捕获」
有天上飞的概念,就要有落地的实现 概念十遍不如代码一遍,朋友,希望你把文中所有的代码案例都敲一遍 先赞后看,养成习惯 SpringBoot 图文教程系列文章目录 SpringBoot图文教程1-Spr ...
- 结题报告--P5551洛谷--Chino的树学
题目:点此 题目描述 Chino树是一棵具有某种性质的满二叉树,具体来说,对于这棵树的每一个非叶子节点,它的左子节点(A)(A)(A)的右子节点(C)(C)(C)与它的右子节点(B)(B)(B)的左子 ...
- 有关于python内置函数exec和eval一些见解笔记
eval是将函数内的字符串以计算式的方式进行计算并给与外部一个值. 例: a=eval('1+1') print(a) >>>>2 但是如果出现在函数内部字符串中进行赋值会抛出 ...