一、屏幕显示图像原理

首先明确两个概念:水平同步信号、垂直同步信号。

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 上。

对于只需要圆角的某些场合,可以用一张已经绘制好的圆角图片覆盖到原视图上来模拟出相同的视觉效果。

最彻底的做法:把需要显示的图形在后台线程绘制为图片,避免使用圆角、阴影、遮罩等属性。

五、文章

幸运的芳1990 & 浅谈iOS页面流畅技巧

iOS 页面流畅技巧(1)的更多相关文章

  1. iOS 页面流畅技巧(2)

    一.屏幕显示图像的原理 首先从过去的 CRT 显示器原理说起.CRT 的电子枪按照上面方式,从上到下一行行扫描,扫描完成后显示器就呈现一帧画面,随后电子枪回到初始位置继续下一次扫描.为了把显示器的显示 ...

  2. iOS界面流畅技巧之微博 Demo 性能优化技巧

    微博 Demo 性能优化技巧 我为了演示 YYKit 的功能,实现了微博和 Twitter 的 Demo,并为它们做了不少性能优化,下面就是优化时用到的一些技巧. 预排版 当获取到 API JSON ...

  3. iOS开发实用技巧—项目新特性页面的处理

    iOS开发实用技巧篇—项目新特性页面的处理 说明:本文主要说明在项目开发中会涉及到的最最简单的新特性界面(实用UIScrollView展示多张图片的轮播)的处理. 代码示例: 新建一个专门的处理新特性 ...

  4. iOS:小技巧(不断更新)

    记录下一些不常用技巧,以防忘记,复制用. 1.获取当前的View在Window的frame: UIWindow * window=[[[UIApplication sharedApplication] ...

  5. iOS开发实用技巧—在手机浏览器头部弹出app应用下载提示

    iOS开发实用技巧—在手机浏览器头部弹出app应用下载提示 本文介绍其简单使用: 第一步:在本地建立一个访问的服务端.  打开本地终端,在本地新建一个文件夹,在该文件夹中存放测试的html页面.   ...

  6. [转] iOS性能优化技巧

    (转自:hhttp://www.raywenderlich.com/31166/25-ios-app-performance-tips-tricks#arc, http://blog.ibireme. ...

  7. iOS:小技巧(19-02-12更)

    记录下一些不常用技巧,以防忘记,复制用. 1.UIImageView 和UILabel 等一些控件,需要加这句才能成功setCorn _myLabel.layer.masksToBounds = YE ...

  8. iOS页面传值-wang

    iOS页面间传值的方式(NSUserDefault/Delegate/NSNotification/Block/单例) 实现了以下iOS页面间传值:1.委托delegate方式:2.通知notific ...

  9. iOS页面间传值的方式(Delegate/NSNotification/Block/NSUserDefault/单例)

    iOS页面间传值实现方法:1.通过设置属性,实现页面间传值:2.委托delegate方式:3.通知notification方式:4.block方式:5.UserDefault或者文件方式:6.单例模式 ...

随机推荐

  1. 利用机器学习检测HTTP恶意外连流量

    本文通过使用机器学习算法来检测HTTP的恶意外连流量,算法通过学习恶意样本间的相似性将各个恶意家族的恶意流量聚类为不同的模板.并可以通过模板发现未知的恶意流量.实验显示算法有较好的检测率和泛化能力. ...

  2. tomcat服务器的应用总结

    tomcat的安装和部署: >> Web的基本入门: |-- C/S架构:客户端和服务器: |-- B/S架构:浏览器和服务器: >> 服务器当中可以放入的资源: |-- 静态 ...

  3. node--CommonJS

    1.CommonJS 1)弥补js没有标准的缺陷 2.Node模块 1)分为核心模块和用户自定义模块 2)我们可以把公共的功能抽离为一个单独的js文件作为一个模块 其中的成员和属性外界无法访问,若要设 ...

  4. Bootstrap4 正式发布

    历经三年开发,前端框架Bootstrap 4正式发布了.然而今天的Web世界已经和当初Mark Otto发布Bootstrap时的情况大为不同,一些开发者由此质疑它的更新是否还有意义 1.V4版本的主 ...

  5. html+css+js+Hbuilder开发一款安卓APP,根本不用学Android开发!

    我们知道,要做一款安卓APP,咱们得先学安卓开发语言,例如java,前端后端.那么没有这些开发语言基础,咱们怎么做呢?其实现在有比较好的开发方案就是做webAPP,咱们可以用web前端知识构建安卓客户 ...

  6. 微信小程序中图片上传阿里云Oss

    本人今年6月份毕业,最近刚在上海一家小公司实习,做微信小程序开发.最近工作遇到一个小问题. 微信小程序图片上传阿里云服务器Oss也折腾了蛮久才解决的,所以特意去记录一下. 第一步:配置阿里云地址: 我 ...

  7. 前端每日实战:7# 视频演示如何用纯 CSS 创作一个 3D 文字跑马灯特效

    效果预览 按下右侧的"点击预览"按钮在当前页面预览,点击链接全屏预览. https://codepen.io/zhang-ou/pen/GdrrZq 可交互视频教程 此视频是可以交 ...

  8. Asp.Net Core 中IdentityServer4 授权中心之应用实战

    一.前言 查阅了大多数相关资料,查阅到的IdentityServer4 的相关文章大多是比较简单并且多是翻译官网的文档编写的,我这里在 Asp.Net Core 中IdentityServer4 的应 ...

  9. webpack的loader和plugin的区别

    [Loader]:用于对模块源码的转换,loader描述了webpack如何处理非javascript模块,并且在buld中引入这些依赖.loader可以将文件从不同的语言(如TypeScript)转 ...

  10. 手机app抓包工具,安卓手机adb无线连接

    默认手机已经usb调试配置完成 网络必须在同一网络中,每次断开wifi连接,都必须重新重做一次 使用数据线连接电脑 cmd 打开一个命令行 输入 # abd如果没有配置环境变量,请配置或者进入adb文 ...