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.单例模式 ...
随机推荐
- PyCharm+git+码云实现project版本控制
1.安装git https://git-scm.com/downloads 2.PyCharm中配置 3.申请码云 4.PyCharm中安装码云插件 右键选择,重启Pycharm. 重新打开PyCha ...
- go语言指南之斐波纳契闭包
练习:斐波纳契闭包 让我们用函数做些好玩的事情. 实现一个 fibonacci 函数,它返回一个函数(闭包),该闭包返回一个斐波纳契数列 `(0, 1, 1, 2, 3, 5, ...)`. 这是一个 ...
- css中grid属性的使用
grid布局 加在父元素上的属性 grid-template-columns/grid-template-rows 定义元素的行或列的宽高 如果父元素被等分成了9等分,则,不管有多少个子元素,都显示9 ...
- emmet快速缩写展开的基本写法与心得
emmet的基本写法: .ct 点是class <div class="ct"></div> #ct 井号是id <div id="ct&q ...
- 大型Java进阶专题(一) 前言
前言 各位读者好,本系列为Java进阶专题,为那些有一定工作经验,做了多年业务的码农,希望突破技术瓶颈,但没有形成系统的Java只是体系,缺乏清晰的提升方法和学习路径的人,比如作者本人.该课题的是 ...
- JDK8内存模型—消失的PermGen
一.JVM 内存模型 根据 JVM 规范,JVM 内存共分为虚拟机栈.堆.方法区.程序计数器.本地方法栈五个部分. 1.虚拟机栈:每个线程有一个私有的栈,随着线程的创建而创建.栈里面存着的是一种叫“栈 ...
- SSL/TLS 协议运行机制概述(一)
SSL/TLS 协议运行机制概述(一) SSL/TLS 发展史 1994年,NetScape 设计了SSL协议(Secure Sockets Layer) 1.0,未正式发布 1995年,NetSca ...
- Django _web框架本质
Web框架本质 我们可以这样理解:所有的Web应用本质上就是一个socket服务端,而用户的浏览器就是一个socket客户端. 这样我们就可以自己实现Web框架了. socket服务端 import ...
- GO系列 | 5分钟入门GO【译】
什么是Google Go? Google Go是由Robert Griesmer,Rob Pike和Ken Thompson在Google设计的一种开源编程语言. Go在语法上类似于C语言: 除了内存 ...
- 浅谈 HTTP中Get与Post的区别
浅谈 HTTP中Get与Post的区别 存在的误区 有人说 HTTP 协议下的 Get 请求参数长度是有大小限制的,最大不能超过XX,而 Post 是无限制的,看到这里,我想他们定是看多了一些以讹传讹 ...