当前位置: 首页 > news >正文

iOS问题整理08----性能优化

本篇主要解决的问题

  • 优化你是从哪几方面着手?
  • 在屏幕成像的过程中,CPU 和 GPU 分别负责处理哪些事情?针对界面的流畅度可以做什么优化?
  • 如何减少启动时间?
  • 安装包如何瘦身?

优化你是从哪几方面着手?

  • 内存优化
  • 卡顿优化
  • 耗电优化
    • 降低 CPU、GPU 功耗
    • 少用定时器
    • 优化 I/O 操作
      • 尽量不要频繁写入小数据,最好批量一次性写入
      • 读写大量重要数据时,考虑使用dispatch_io,其提供了基于 GCD 的异步操作文件 I/O 的 API。用 dispatch_io 系统会优化磁盘访问
      • 数据量比较大的,建议使用数据库(比如 SQLite、CoreDota)
    • 网络优化
      • 减少、压缩网络数据,比如 XML 换成 JSON,现在也有公司在 protobuf,比如上传的文件压缩之后再传
      • 多次请求结果是相同的,尽量使用缓存,NSCache
      • 尽量使用断点续传,避免重复下载
      • 网络不可用的时候,不要尝试执行网络请求
      • 设置合适的超时时间
    • 定位优化
    • 硬件检测优化
      • 用户移动、摇晃、倾斜设备时,会产生动作(motion)事件,这些事件由加速计、陀螺仪、磁力计等硬件检测。在不需要检测的场合,应该及时关闭这些硬件。
  • 启动时间优化
  • 安装包大小优化

在屏幕成像的过程中,CPU 和 GPU 分别负责处理哪些事情?针对界面的流畅度可以做什么优化?

屏幕上的任何内容要想显示出来,都需要经过 CPU 和 GPU 的处理:

  • CPU 计算好数后提交给 GPU
  • GPU 对计算好的数据进行渲染,渲染成能显示的数据并后放入帧缓存
  • 视频控制器器按照 VSync 信号逐行读取缓冲区中数据,经过数模转换后传递给显示器显示

双缓冲区机制

为解决效率问题,iOS 使用的是双缓冲区机制。GPU 会先渲染好一帧放入一个缓冲区,供视频控制器读取;GPU 的下一帧渲染好之后会放入另一个缓冲区,这时视频控制器的指针会指向这一个缓冲区。这样做的弊端是:如果视频控制器在读取的过程中(即屏幕只显示了一部分),GPU 完成了新一帧的绘制,那么就会造成画面撕裂的现象。为解决这个问题引入了 VSync 机制,当视屏控制器读取完一帧之后才会发出 VSync 信号,GPU 才会绘制新的一帧,完成后缓冲区才会更新。

按照 60FPS 的帧率,每隔 16ms 就会有一次 VSync 信号。

卡顿的原因

VSync 信号到来时,系统通过 CADisplayLink 等机制通知 App,App 开始在 CPU 中计算显示内容并提交给 GPU,GPU 完成渲染后提交给缓冲区。

如果在这个过程中由来了第二个 VSync 信号,也就是说 CPU 或者 GPU 的计算时间过长,在第二个垂直同步信号来之前还没有往缓冲区提交新内容。那么,屏幕会保留之前的显示内容,正在计算的这一帧,会等待下一次机会才能显示。

这就是丢帧。就是说,卡顿的原因就是 CPU 或者 GPU 的计算时间过长,导致当垂直同步信号到来时还没有往双缓冲区提交新的内容,屏幕会保留之前的内容,新内容只能等下一个垂直同步信号才有机会显示。

示意图如下:

了解了上面的过程,我们就知道卡顿优化主要是要针对 CPU 与 GPU 进行优化。

CPU (Central Processing Unit,中央处理器)功能及优化

  • 对象的创建和销毁
    • 尽量使用轻量级对象。比如在不需要响应触摸事件的时候使用 CALayer 代替 UIView
    • 使用 xib 会更消耗性能,在性能非常敏感的界面,可以放弃使用 xib
    • 尽量推迟对象的创建,把对象的创建分散到多个任务中去
  • 对象属性的调整
    • UIView 的属性调整远大于一般对象的属性调整,要尽量减少属性的的调整
    • 尽量避免调整视图的层次、添加和移除视图
  • 布局计算
    • UIView 属性调整已经很消耗性能了,如果再修改frameboundstransform等属性,那么 CPU 和 GPU 又会重新计算和渲染,更消耗性能了,所以这些属性要尽量减少调整次数。
    • AutoLayout 的带来的 CPU 消耗会随着视图数量的增长呈指数级增长。
    • UIImageViewsize 最好与图片的大小保持一致(避免更多计算)
  • 文本的计算和排版
    • 如果界面中有大量文本,那么文本的宽高计算也会消耗大量资源。可以将宽高计算与绘制文本放到子线程中去处理。
  • 图片的格式转换和解码
    • UIImage 创建图片的时候,图片并不会立即解码,而是在 CALayer 提交到 GPU 的前一刻才进行解码,这一步是在主线程中。要想绕开这个机制,常见的做法是自己在子线程中进行解码,常见的第三方库都有这个功能。
  • 图像的绘制(Core Graphics)
    • 一些以 CG 开头的绘制图像的方法也可以放到子线程中去

GPU (Graphics Processing Unit,图形处理器)及优化

  • 纹理的渲染
    • 尽量避免短时间内大量图片的显示,可以将多张图片合成一张图片
    • GPU 能处理的最大纹理尺寸是 4096x4096 一旦超过这个值,就会占用 CPU 的资源进行处理
    • 减少视图层级
    • 尽量避免透明度小于 1 的视图
    • 尽量避免离屏渲染、离屏渲染更详细
      • 触发离屏渲染的操作
        • 主动触发:光栅化 layer.shouldRasterize
        • layer.shadowlayer.masklayer.borderlayer 设置圆角
        • 重写内存恶鬼 drawRect: 方法时,视图只要设置背景颜色,也会触发离屏渲染
        • CAShapeLayer 的矢量图形显示
        • Core Graphics API (核心绘图)的绘制操作会导致 CPU 的离屏渲染。
      • 如何高效设置 ImageView 的圆角

卡顿检测

  • 添加 Observer 到主线程 RunLoop 中,通过监听 RunLoop 状态切换的好时,以达到监控卡顿的目的。点击这里查看高清大图。

你在项目中是怎么优化内存的?

tableview 卡顿的原因可能又哪些?你平时是怎么优化的?

如何减少启动时间?

APP 的启动分为两种:

  • 冷启动:从零启动
  • 热启动:APP 已经在内存中,在后台活着,再次点击 APP 启动 APP

APP 的启动时间主要是针对冷启动来说的。启动时间主要由main()之前和main()之后两部分组成。

分析main()之前的时间消耗

在 Edit scheme -> Run -> Arguments 中添加环境变量 DYLD_PRINT_STATISTICS 且设置值为 1。(按下 cmd + shift + , 快捷呼出 Edit scheme )

现在启动程序就可以看到打印出来的信息了。

如果设置 DYLD_PRINT_STATISTICS_DETAILS1,可以看到更详细的信息。

APP 的启动过程可以分为如图所示的三大阶段:dyld 阶段、runtime 阶段、main 阶段

启动 APP 时,dyld 做的事情有:(dyld ,dynamic link editor, Apple 的动态连接器,可以用来装载 Mach-O 文件,可执行文件、动态库等都属于 Mach-O 文件)

  • 装载 APP 的可执行文件,同时会递归加载所有依赖的动态库
  • dyld 把可执行文件、动态库都装载完毕后,会通知 runtime 进行下一步的处理。

启动APP时,runtime 所做的事情有:

  • 调用 map_images 进行可执行文件内容的解析和处理
  • load_images 中调用 call_load_methods,调用所有 ClassCategory+load 方法
  • 进行各种 objc 结构的初始化(注册 objc 类、初始化类对象等等)
  • 调用 C++ 静态初始化器和 __attribute__((constructor))修饰的函数

到此为止,可执行文件和动态库中所有的符号(Class, Protocol, Selector, IMP,...)都已经按格式成功加载到内存中,被 runtime 所管理。

main() 阶段:

main 函数的实现很简单,如下:

int main(int argc, char * argv[]) {
    @autoreleasepool {
        return UIApplicationMain(argc, argv, nil NSStringFromClass([AppDelegate class]));
    }
}
复制代码

当代码走到 UIApplicationMain 函数时,就会走到 Appdelegate 类里。然后就会调用 application:didFinishLaunchingWithOptions:

App启动过程中每一个步骤都会影响启动性能,但是有些部分所消耗的时间少之又少,另外有些部分根本无法避免,考虑到投入产出比,我们只列出我们可以优化的部分:

  • dyld
    • 减少动态库(定期清理不必要的动态库)
    • 减少 Objc 类、分类的数量(定期清理不必要的类、分类)
    • 减少 C++ 虚函数数量
    • Swift 尽量使用 struct
  • runtime
    • 尽量不要用到 +load 方法
    • 尽量不要用到__attribute__((constructor))的C函数
    • 也尽量不要用到C++的静态对象
  • main
    • 在不影响用户体验的前提下,尽可能将一些操作延迟,不要全部都放在 finishLaunching 方法中
    • 按需加载

安装包如何瘦身?

相关文章:

  • DBA需要掌握的shell知识
  • Mysql学习笔记(六)增删改查
  • 如何撰写好文档?精益文档的六个实践
  • 最最最常见的Java面试题总结-第一周
  • 耗时一个月,我为拉勾设计的移动端
  • NGUI学习笔记(一):官方视频学习记录
  • 个推用户画像产品 (个像) Android 集成实践
  • asp.net下使用Cookie保存登录信息
  • SQLServer插入数据
  • Sql Xml
  • Notepad++的语法高亮
  • 电脑安装打印机设备搜索不到解决记录
  • JMX详解
  • Spring Security 基于表达式的权限控制
  • Storm 0.9 集群搭建
  • JS中 map, filter, some, every, forEach, for in, for of 用法总结
  • 2018以太坊智能合约编程语言solidity的最佳IDEs
  • Bootstrap JS插件Alert源码分析
  • CentOS学习笔记 - 12. Nginx搭建Centos7.5远程repo
  • Eureka 2.0 开源流产,真的对你影响很大吗?
  • golang 发送GET和POST示例
  • Java 9 被无情抛弃,Java 8 直接升级到 Java 10!!
  • Java的Interrupt与线程中断
  • LintCode 31. partitionArray 数组划分
  • Meteor的表单提交:Form
  • niucms就是以城市为分割单位,在上面 小区/乡村/同城论坛+58+团购
  • Object.assign方法不能实现深复制
  • RxJS: 简单入门
  • SQLServer之索引简介
  • vue.js框架原理浅析
  • Vue学习第二天
  • 半理解系列--Promise的进化史
  • 创建一种深思熟虑的文化
  • 分享一个自己写的基于canvas的原生js图片爆炸插件
  • 高度不固定时垂直居中
  • 给Prometheus造假数据的方法
  • 如何优雅的使用vue+Dcloud(Hbuild)开发混合app
  • 使用Maven插件构建SpringBoot项目,生成Docker镜像push到DockerHub上
  • 栈实现走出迷宫(C++)
  • [Shell 脚本] 备份网站文件至OSS服务(纯shell脚本无sdk) ...
  • ​html.parser --- 简单的 HTML 和 XHTML 解析器​
  • ### Error querying database. Cause: com.mysql.jdbc.exceptions.jdbc4.CommunicationsException
  • (二十五)admin-boot项目之集成消息队列Rabbitmq
  • (附源码)ssm基于jsp的在线点餐系统 毕业设计 111016
  • (附源码)计算机毕业设计SSM在线影视购票系统
  • (九)信息融合方式简介
  • (一)插入排序
  • (转)清华学霸演讲稿:永远不要说你已经尽力了
  • .Net Remoting(分离服务程序实现) - Part.3
  • .net Stream篇(六)
  • .NET 的程序集加载上下文
  • .net 写了一个支持重试、熔断和超时策略的 HttpClient 实例池
  • .NET开发人员必知的八个网站
  • .NET中使用Redis (二)
  • /etc/sudoers (root权限管理)