iOS App 启动性能优化
1. App启动过程
解析Info.plist
加载相关信息,例如如闪屏
沙箱建立、权限检查
Mach-O加载
如果是胖二进制文件,寻找合适当前CPU类别的部分
加载所有依赖的Mach-O文件(递归调用Mach-O加载的方法)
定位内部、外部指针引用,例如字符串、函数等
执行声明为
__attribute__((constructor))
的C函数加载类扩展(Category)中的方法
C++静态对象加载、调用ObjC的
+load
函数
程序执行
调用
main()
调用
UIApplicationMain()
调用
applicationWillFinishLaunching
2. 如何测量启动过程耗时
冷启动比热启动重要
当用户按下home键的时候,iOS的App并不会马上被kill掉,还会继续存活若干时间。理想情况下,用户点击App的图标再次回来的时候,App几乎不需要做什么,就可以还原到退出前的状态,继续为用户服务。这种持续存活的情况下启动App,我们称为热启动,相对而言冷启动就是App被kill掉以后一切从头开始启动的过程。我们这里只讨论App冷启动的情况。
main()函数之前
在不越狱的情况下,以往很难精确的测量在main()函数之前的启动耗时,因而我们也往往容易忽略掉这部分数据。小型App确实不需要太过关注这部分。但如果是大型App(自定义的动态库超过50个、或编译结果二进制文件超过30MB),这部分耗时将会变得突出。所幸,苹果已经在Xcode中加入这部分的支持。
苹果提供的方法
在Xcode的菜单中选择
Project
→Scheme
→Edit Scheme...
,然后找到Run
→Environment Variables
→+
,添加name为DYLD_PRINT_STATISTICSvalue
为1
的环境变量。
在Xcode运行App时,会在console中得到一个报告。例如,我在WiFi管家中加入以上设置之后,会得到这样一个报告:
Total pre-main time: 94.33 milliseconds (100.0%)
dylib loading time: 61.87 milliseconds (65.5%)
rebase/binding time: 3.09 milliseconds (3.2%)
ObjC setup time: 10.78 milliseconds (11.4%)
initializer time: 18.50 milliseconds (19.6%)
slowest intializers :
libSystem.B.dylib : 3.59 milliseconds (3.8%)
libBacktraceRecording.dylib : 3.65 milliseconds (3.8%)
GTFreeWifi : 7.09 milliseconds (7.5%)
如何解读
main()函数之前总共使用了94.33ms
在94.33ms中,加载动态库用了61.87ms,指针重定位使用了3.09ms,ObjC类初始化使用了10.78ms,各种初始化使用了18.50ms。
在初始化耗费的18.50ms中,用时最多的三个初始化是libSystem.B.dylib、libBacktraceRecording.dylib以及GTFreeWifi。
main()函数之后
从main()
函数开始至applicationWillFinishLaunching
结束,我们统一称为main()函数之后的部分。
3. 影响启动性能的因素
App启动过程中每一个步骤都会影响启动性能,但是有些部分所消耗的时间少之又少,另外有些部分根本无法避免,考虑到投入产出比,我们只列出我们可以优化的部分:
main()函数之前耗时的影响因素
动态库加载越多,启动越慢。
ObjC类越多,启动越慢
C的constructor函数越多,启动越慢
C++静态对象越多,启动越慢
ObjC的+load越多,启动越慢
实验证明,在ObjC类的数目一样多的情况下,需要加载的动态库越多,App启动就越慢。同样的,在动态库一样多的情况下,ObjC的类越多,App的启动也越慢。需要加载的动态库从1个上升到10个的时候,用户几乎感知不到任何分别,但从10个上升到100个的时候就会变得十分明显。同理,100个类和1000个类,可能也很难查察觉得出,但1000个类和10000个类的分别就开始明显起来。
同样的,尽量不要写__attribute__((constructor))
的C函数,也尽量不要用到C++的静态对象;至于ObjC的+load
方法,似乎大家已经习惯不用它了。任何情况下,能用dispatch_once()
来完成的,就尽量不要用到以上的方法。
main()函数之后耗时的影响因素
执行main()函数的耗时
执行applicationWillFinishLaunching的耗时
rootViewController及其childViewController的加载、view及其subviews的加载
优化的目标
由于每个App的情况有所不同,需要加载的数据量也有所不同,事实上我们无法使用一种统一的标准来衡量不同的App。苹果。
应该在400ms内完成main()函数之前的加载
整体过程耗时不能超过20秒,否则系统会kill掉进程,App启动失败
400ms内完成main()函数前的加载的建议值是怎样定出来的呢?其实我也没有太深究过这个问题,但是,当用户点击了一个App的图标时,iOS做动画到闪屏图出现的时长正好是这个数字,我想也许跟这个有关。
针对不同规模的App,我们的目标应该有所取舍。例如,对于像手机QQ这种集整个SNG的代码大成撸出来的App,对动态库的使用在所难免,但对于WiFi管家,由于在用户连接WiFi的时候需要非常快速的响应,所以快速启动就非常重要。
那么,如何定制优化的目标呢?首先,要确定启动性能的界限,例如,在各种App性能的指标中,哪一此属于启动性能的范畴,哪一些则于App的流畅度性能?我认为应该首先把启动过程分为四个部分:
main()函数之前
main()函数之后至applicationWillFinishLaunching完成
App完成所有本地数据的加载并将相应的信息展示给用户
App完成所有联网数据的加载并将相应的信息展示给用户
1+2一起决定了我们需要用户等待多久才能出现一个主视图,同时也是技术上可以精确测量的时长,1+2+3决定了用户视觉上的等待出现有用信息所需要的时长,1+2+3+4决定了我们需要多少时间才能让我们需要展示给用户的所有信息全部出现。
淘宝的iOS客户端无疑是各部分都做得非常优秀的典型。它所承载的业务完全不比微信和手机QQ少,但几乎瞬间完成了启动,并利用缓存机制使得用户马上看到“貌似完整”的界面,然后立即又刷新了刚刚联网更新回来的信息。也就是说,无论是技术上还是视觉上,它都非常的“快”。
启动优化实践
1. 移除不需要用到的动态库
2. 移除不需要用到的类
3. 合并功能类似的类和扩展(Category)
4. 压缩资源图片
5. 优化applicationWillFinishLaunching
6. 优化rootViewController加载
iOS App 启动性能优化的更多相关文章
- 马蜂窝 iOS App 启动治理:回归用户体验
增长.活跃.留存是移动 App 的常见核心指标,直接反映一款 App 甚至一个互联网公司运行的健康程度和发展动能.启动流程的体验决定了用户的第一印象,在一定程度上影响了用户活跃度和留存率.因此,确保启 ...
- 李洪强iOS开发之性能优化技巧
李洪强iOS开发之性能优化技巧 通过静态 Analyze 工具,以及运行时 Profile 工具分析性能瓶颈,并进行性能优化.结合本人在开发中遇到的问题,可以从以下几个方面进行性能优化. 一.view ...
- iOS 25个性能优化/内存优化常用方法
1. 用ARC管理内存 ARC(Automatic ReferenceCounting, 自动引用计数)和iOS5一起发布,它避免了最常见的也就是经常是由于我们忘记释放内存所造成的内存泄露.它自动为你 ...
- [iOS Animation]-CALayer 性能优化
性能优化 代码应该运行的尽量快,而不是更快 - 理查德 在第一和第二部分,我们了解了Core Animation提供的关于绘制和动画的一些特性.Core Animation功能和性能都非常强大,但如果 ...
- iOS面试-关于性能优化
目录 我要给出的建议将分为三个不同的等级: 入门级. 中级和进阶级: 入门级(这是些你一定会经常用在你app开发中的建议) 1. 用ARC管理内存2. 在正确的地方使用reuseIdentifier3 ...
- IOS (APP 启动 相应处理)
APP 每次启动的入口都是通过: - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSD ...
- 复杂TableView在iOS上的性能优化
声明:本文翻译自<iOS performance optimization>,原文作者 Khang Vo.翻译本文纯属为了技术交流的目的,并不具有任何的商业性质,也不得利用本文内容进行商业 ...
- Windows 程序启动性能优化(先载入EXE,后载入DLL,只取有限的代码载入内存,将CPU的IP指向程序的入口点)
一.重定位链接时重定位:目标文件一般由多个节组成,编译器在编译每个目标文件时一般都是从0地址开始生成代码.当多个代码节合成一个代码段时,需要根据其在最终代码段中的位置做出调整.同时,链接器需要对已经解 ...
- appium + java + WebDriverAgent实现IOS app启动
Appium v1.8.1 <dependency> <groupId>io.appium</groupId> <artifactId>ja ...
随机推荐
- JSON对象转换成JSON字符串
1.问题背景 有一个json对象,需要将其转换成json字符串 JSON.stringify(obj) 2.实现源码 <!DOCTYPE html PUBLIC "-//W3C//DT ...
- Axure RP一个专业的快速原型设计工具
Axure RP是一个专业的快速原型设计工具.Axure(发音:Ack-sure),代表美国Axure公司:RP则是Rapid Prototyping(快速原型)的缩写. Axure简要介绍 Axur ...
- CodeM资格赛 Round A 最长树链
按照题解的做法,对于每一个质约数分别进行讨论最长链就行 对于每一个数的质约数可是比logn还要小的 比赛的时候没人写,我也没看 = =,可惜了,不过我当时对于复杂度的把握也不大啊 #include & ...
- 如何登录mysql? cmd怎么连接mysql数据库||从MYSQL客户端登录MYSQL
1 2 3 4 5 6 7 分步阅读 Mysql开源数据库,任何人都可以下载安装使用.那么安装好的mysql如何登陆连接mysql数据库呢?本经验咗嚛介绍几种常见的方法 工具/原料 mysql 连 ...
- Mybatis常见面试题
Mybatis常见面试题 #{}和${}的区别是什么? #{}和${}的区别是什么? 在Mybatis中,有两种占位符 #{}解析传递进来的参数数据 ${}对传递进来的参数原样拼接在SQL中 #{}是 ...
- Qtree3题解(树链剖分(伪)+线段树+set)
外话:最近洛谷加了好多好题啊...原题入口 这题好像是SPOJ的题,挺不错的.看没有题解还是来一篇... 题意: 很明显吧.. 题解: 我的做法十分的暴力:树链剖分(伪)+线段树+\(set\)... ...
- 【BZOJ2337】Xor和路径(高斯消元)
[BZOJ2337]Xor和路径(高斯消元) 题面 BZOJ 题解 我应该多学点套路: 对于xor之类的位运算,要想到每一位拆开算贡献 所以,对于每一位拆开来看 好了,既然是按位来算 我们就只需要计算 ...
- Java 多线程之哪个对象才是锁?
问题背景 在感觉正常的使用ArrayList的迭代删除的操作的时候,发现了如下的崩溃日志: Caused by: java.util.ConcurrentModificationException a ...
- 26.Django模板语言和分页
继承 extends 子版只能继承一个父模板 1.父模板 master.html <!DOCTYPE html> <html lang="en"> < ...
- 直播-srs起步
srs简介 https://github.com/ossrs/srs/wiki/v2_CN_Home 原料 CentOS Linux release 7.2.1511 (Core) ffmpe ...