服务器断电了,所以得空来写点东西,之所以写这个问题是因为一次乌龙事件;我们项目上线引发了看门狗超时问题,就是那个eatBadfood,然后导致启动时间超时,高达27s,呵呵,啥玩意27s起不来啊,出问题设备是iPad但是我们测试时候没有遇到。
OK,那就是我的问题了吧,不多说启动超时我们就来处理启动超时的问题。
首先就是需要了解关于app启动的时候都干了什么事情,包括我们点击build按钮都干了什么:
当我们点击了 build 之后,做了什么事情呢?
预处理(Pre-process):把宏替换,删除注释,展开头文件,产生 .i 文件。
编译(Compliling):把之前的 .i 文件转换成汇编语言,产生 .s文件。
汇编(Asembly):把汇编语言文件转换为机器码文件,产生 .o 文件。
链接(Link):对.o文件中的对于其他的库的引用的地方进行引用,生成最后的可执行文件(同时也包括多个 .o 文件进行 link)。
其实通常对我们来说的话就是分为编译阶段和执行阶段;在我们app代码里面进行区分就是以main()函数为间隔区分这两个阶段,main()函数执行之前做的事情就是我们所要进行优化的地方了,即pre-main:在这段时间里系统会进行加载动态库、注册 Objc 类等系统操作。
在我们要优化之前首先我们要看一下,我们具体看一下启动时间到底是多少(系统方法):Edit scheme -> Run -> Auguments 将环境变量 DYLD_PRINT_STATISTICS 设为 1)然后运行,控制台就会打印我们的premain所用的时间。
Total pre-main time: 915.59 milliseconds (100.0%)
dylib loading time: 499.70 milliseconds (54.5%)
rebase/binding time: 48.53 milliseconds (5.3%)
ObjC setup time: 24.47 milliseconds (2.6%)
initializer time: 342.87 milliseconds (37.4%)
slowest intializers :
libSystem.B.dylib : 4.06 milliseconds (0.4%)
libMainThreadChecker.dylib : 34.28 milliseconds (3.7%)
libglInterpose.dylib : 124.91 milliseconds (13.6%)
Pretty : 263.99 milliseconds (28.8%)
total time: 1.8 seconds (100.0%)
total images loaded: 573 (523 from dyld shared cache)
total segments mapped: 195, into 13129 pages
total images loading time: 1.2 seconds (66.3%)
total load time in ObjC: 24.47 milliseconds (1.2%)
total debugger pause time: 759.18 milliseconds (40.0%)
total dtrace DOF registration time: 0.00 milliseconds (0.0%)
total rebase fixups: 279,519
total rebase fixups time: 40.52 milliseconds (2.1%)
total binding fixups: 720,435
total binding fixups time: 222.74 milliseconds (11.7%)
total weak binding fixups time: 8.30 milliseconds (0.4%)
total redo shared cached bindings time: 223.03 milliseconds (11.7%)
total bindings lazily fixed up: 0 of 0
total time in initializers and ObjC +load: 342.87 milliseconds (18.0%)
libSystem.B.dylib : 4.06 milliseconds (0.2%)
libBacktraceRecording.dylib : 5.84 milliseconds (0.3%)
libMainThreadChecker.dylib : 34.28 milliseconds (1.8%)
libglInterpose.dylib : 124.91 milliseconds (6.5%)
CoreDuet : 2.50 milliseconds (0.1%)
libMTLCapture.dylib : 2.71 milliseconds (0.1%)
libViewDebuggerSupport.dylib : 8.01 milliseconds (0.4%)
FBSDKCoreKit : 4.42 milliseconds (0.2%)
Pretty : 263.99 milliseconds (13.9%)
total symbol trie searches: 1703506
total symbol table binary searches: 0
total images defining weak symbols: 59
total images using weak symbols: 136
其实看起来还是很乱七八糟的,因为我在environment里添加了另外一行:DYLD_PRINT_STATISTICS_DETAILS为YES,然后打印的就如上这么详细了。
反正我当时看的时候是一脸懵逼,这都是啥,多亏了有大神指点,我们可以来详细的分析:
pre-main时间主要由 4 部分组成(原文搬运):
1.dylib loading:
这一阶段 dyld 会分析应用依赖的 dylib ,所以,依赖的 dylib 越少越好。在这一步,我们能做的优化就是检查是否存在不需要的 dylib ,移除不必要的 dylib 。
在项目优化实践中,我们移除了一个没有必要的动态库,并将几个动态库合成为一个动态库,减少动态库数量。
2.rebase/binding:
这一阶段系统主要注册 Objc 类。所以,指针数量越少越好。这一步能做的优化有:
清理项目中无用的类
删减没有被调用到或者已经废弃的方法
删减一些无用的静态变量
可通过 AppCode 等工具扫描项目中未使用的代码。
3.Objc srtup:
这一阶段没有什么特别能优化的地方,如果 rebase/binding 阶段优化的好这步耗时也会很少。
4.initializer:
这一阶段,dyld 开始运行程序的初始化函数,调用每个 Objc 类和分类的 +load 方法,调用 C/C++ 中的构造器函数。initializer阶段执行完后,dyld 开始调用 main() 函数。在这一步,检查 +load 方法,尽量把事情推迟到 +initiailize 方法里执行。
在这里我们修改了部分原本代码中直接在 +load 函数初始化逻辑改为在 +initialize 中加载,也就是到使用时才加载。
参考以上的教程,我寻找了我们app的需要优化的地方,拼命减少了一个动态库,还有几个三方库,把一些用来测试的指针都给干掉了,当然因为是新的app,所以没有用app code检测,以后可以使用试试。
虽然我没看出来启动时间有啥进步,但是改了就是进步了啊;美滋滋提交,求神拜佛,结果同样问题,同样结果。
我就开始总结,这个问题一开始就和启动时间没有关系啊,再超时也不可能27s嘛,第二次打回来告诉我32s,这就更离谱了,所以我进行了错误日志解析,呵呵,原来是三方库的问题(百度地图sdk导致);所以建议在finishLaunch的时候部分三方注册,代理的内容可以放到子线程做。
我们回过头来看启动时间的问题,我们通过上面对照能够看懂我们程序所打印的内容,如果能够获取程序在各个页面显示出来所占用的时间,然后我们才能进行具体优化,判断是否有我们肉眼所看不到的卡顿问题。
下面是正文:
1.premain()以后的显示的页面时间优化
(1).在我们首页显示之前,尽量减少操作。
例如:各种网络请求,能省就省了吧;能用代码构建UI,尽量少用xib;手势等添加可以在页面显示之后;减少加载的view controller的加载数量;部分可以延时处理的操作可以放到子线程。
至于使用动画,个人觉得就算了吧,如果不是特别熟练清楚底层原理,建议不要使用(动画不会被主线程阻塞)
(2).页面卡顿的优化
这里感觉能优化的就有很多了,可以通过bugly来监测一些卡顿问题,通过具体代码去解决问题。
还有关于CPU和GPU,页面渲染相关的问题我也正在学习阶段,建议大家多读大神文章,一起学习吧。毕竟我也是菜的不行。
2.页面加载耗时打印
打印各个页面加载时间,有助于我们发现很多平时看不到的问题,对比得到哪些页面需要去优化。
下面我来介绍一下具体方法:
我们想要了解加载页面的view消耗的时间需要知道VC的生命周期:
1.initWithCoder: 通过 nib 文件初始化时触发。
2.awakeFromNib: nib 文件被加载的时候,会发生一个 awakeFromNib 的消息到 nib 文件中的每个对象。
3.loadView: 开始加载视图控制器自带的 view。
4.viewDidLoad: 视图控制器的 view 被加载完成。
5.viewWillAppear: 视图控制器的 view 将要显示在 window 上。
6.updateViewConstraints: 视图控制器的 view 开始更新 AutoLayout 约束。
7.viewWillLayoutSubviews:视图控制器的 view 将要更新内容视图的位置。
8.viewDidLayoutSubviews: 视图控制器的 view 已经更新视图的位置。
9.viewDidAppear: 视图控制器的 view 已经展示到 window 上。
10.viewWillDisappear: 视图控制器的 view 将要从 window 上消失。
11.viewDidDisappear: 视图控制器的 view 已经从 window 上消失。
所以,我们只需要在loadview的时候打印一下当前时间[[NSDate date] timeIntervalSince1970],因为获取到的时间单位是秒,建议*1000换算成毫秒,看起来比较直观。在viewDidAppear里面再打印一次当前时间,两个时间的差值就是页面的加载时间啦,如果加载时间太久,那就需要进行优化了。(也得根据自己的代码具体细节进行调整,建议在loadview进行UI布局)
刚刚打印了一下时间:当前开始加载1577944446238
当前结束加载1577944446251
这不是扯么,速度这么快?我都不信,但是这是事实,这是没有进行网络加载的,单纯的加载UI的时间,不包括刷新方法;你需要在你需要的地方进行布局埋点,统计这个页面的加载时间。
有更好的方法的小伙伴,欢迎留言。
参考:https://www.jianshu.com/p/6387eba2ea16