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.单例模式 ...
随机推荐
- UVA - 10462 Is There A Second Way Left?
题意: 给你一张无向图,让你判断三种情况:1.不是连通图(无法形成生成树)2.只能生成唯一的生成树 3.能生成的生成树不唯一(有次小生成树),这种情况要求出次小生成树的边权值和. 思路: 比较常见的次 ...
- 关于localStorage面试的那点事
最近面试的时候关于html5API总会被问到localStorage的问题, 对于一般的问题很简单,无非就是 localStorage.sessionStorage和cookie这三个客户端缓存的区别 ...
- 原生js实现在表格用鼠标框选并有反选功能
今天应同学要求,需要写一个像Excel那样框选高亮,并且实现框选区域实现反选功能.要我用原生js写,由于没什么经验翻阅了很多资料,第一次写文章希望各位指出不足!! 上来先建表 <div clas ...
- GZOJ 1361. 国王游戏【NOIP2012提高组DAY1】
国王游戏[NOIP2012提高组DAY1] Time Limit:1000MS Memory Limit:128000K Description 国王游戏(game.cpp/c/pas) [问题描述] ...
- 搭建flutter开发
最近入坑flutter,dart还没开始学,搭环境就干了我一天半,不容易,记录一下, 我们先立个目标,这是我已经配好的,我是真的有强迫症,需要打四个对勾,真的不容易,我们一个一先说一下每一个都代表什么 ...
- Mac 下 Docker 运行较慢的原因分析及个人见解
在mac 使用 docker 的时候,我总感觉程序在 docker 下运行速度很慢,接下来我一一分析我遇到的问题,希望大家能进行合理的讨论和建议. 问题: valet 下打开 laravel 首页耗时 ...
- Apache Tomcat 文件包含漏洞(CVE-2020-1938)
2月20日,国家信息安全漏洞共享平台(CNVD)发布了Apache Tomcat文件包含漏洞(CNVD-2020-10487/CVE-2020-1938).该漏洞是由于Tomcat AJP协议存在缺陷 ...
- TypeScript Jest 调试
本文简要介绍了如何在 Jest 单元测试中利用 Chrome Node DevTools 来辅助调试. 背景 代码是 TS 写的 所测功能无 UI 界面,出现Bug后不容易定位 用 console 式 ...
- 手撸MyBatis从配置文件到读出数据库的模拟实现
手动模拟MyBatis入门案例的底层实现: 需要了解的关键技术: java反射.动态代理(comming soon) 一.Mybatis入门案例 点击此处跳过入门案例 首先看一下MyBatis最基础的 ...
- C结构体与链表
今天来总结C语言的学习盲点--结构体,为了不显单一,也为了补足作者链表的编程缺陷,特更此博文,总结近段时间的学习成果.话不多说,先上一段代码 struct none{int item; link ne ...