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.单例模式 ...
随机推荐
- http面试问题集锦
1.http的请求报文和响应报文? http请求报文:请求行(请求方法+url).请求头,请求体 http响应报文:状态行(http版本+状态码).响应头.响应体 2.常用的http请求类型? 请 ...
- 利用canvas绘画二级树形结构图
上周需要做一个把页面左侧列表内容拖拽到右侧区域,并且绘制成关系树的功能.看了设计图,第一反应是用canvas绘制关系线.吭哧吭哧搞定这个功能后,发现用canvas绘图,有一个很严重的缺陷.那就是如果左 ...
- react-intl 实现 React 国际化多语言
效果预览 React Intl 国际化步骤 创建国际化资源文件 根据语言获取国际化资源 引入 react-intl 的 local data 创建 LocaleProvider 国际化上下文组件 创建 ...
- 【jQuery学习日记】jQuery实现滚动动画
需要实现的效果 样式分析: 2个主要部分,头部的标题和导航部分,和主要的功能实现区域: 1.头部 <div id="header"> <h1>动漫视频< ...
- [LeetCode] 面试题 10.01.合并排序的数组
题目: 这道题有多种实现的思路,这里使用双指针结合数组有序的特点进行解决 思路: m代表A初始时有效元素的个数,n代表B中元素的个数,那么n+m才是A的总长度 从A的最后一个位置开始,设为cur,分别 ...
- promise迷你书-读书笔记
Promise三种类型 Constructor 使用Promise构造器来实例化一个promise对象 var promise = new Promise(function(resolve,rejec ...
- CMAKE交叉编译快速入门
cmake 工具 cmake 使用非常简单,最常用的用法是 cmake . 在当前目录执行cmake 官方帮助 -D <var>:<type>=<value> -D ...
- Matplotlib数据可视化(6):饼图与箱线图
In [1]: from matplotlib import pyplot as plt import numpy as np import matplotlib as mpl mpl.rcParam ...
- 使用pyecharts绘制词云图-淘宝商品评论展示
一.什么是词云图? 词云图是一种用来展现高频关键词的可视化表达,通过文字.色彩.图形的搭配,产生有冲击力地视觉效果,而且能够传达有价值的信息. 制作词云图的网站有很多,简单方便,适合小批量操作. BI ...
- Python模块三
collections模块 在内置数据类型(dict.list.set.tuple)的基础上,collections模块还提供了几个额外的数据类型:Counter.deque.defaultdict. ...