iOS - 优化App冷启动速度

1. App的启动分为三个主要阶段:

  • main()函数执行前

  • main()函数执行后(从main函数执行,到设置self.window.rootViewController)

  • 首屏渲染完成后(从设置self.window.rootViewController到didFinishLaunchWithOptions方法作用域结束)

main函数执行前,系统会做的事情:
  • 加载可执行文件(App的.o文件集合)

  • 加载动态链接库,进行rebase指针调整和bind符号绑定

  • Objc运行时的初始处理,包括Objc相关类注册、category注册、selector唯一性检查等

  • 初始化,包括了执行+load()方法、attribute((constructor))修饰的函数的调用、创建C++静态全局变量。

main()函数执行后:

main()函数执行后的阶段,指的是从main()函数执行开始,到appDelegate的didFinishLaunchingWithOpentions方法里首屏渲染相关方法执行完成。

这里应该是从功能上梳理出哪些是首屏渲染必要的初始化功能,哪些是App启动必要的初始化功能,哪些是只需要在对应功能开始使用时才需要初始化的,将这些放到各自合适的阶段执行。

首屏渲染完成后:

首屏渲染后的这个阶段,指的是didFinishLaunchWithOptions方法作用域内执行首屏渲染之后的所有方法执行完成,即从 设置了self.window.rootViewController开始 到 didFinishLaunchWithOptions方法作用域 结束。

首屏渲染完成后用户就可以看到App的首页信息了,把这个阶段内卡住主线程的方法解决掉就可以了。

注解:
  • App启动后,首先加载可执行文件,然后加载dyld,然后加载所有依赖库,然后调用所有的+load(),然后调用main(),然后调用UIApplicationMain(),然后调用AppDelegate的代理didFinishLaunchWithOptions.

  • 可执行文件是指Mach-O格式的文件,也就是App中所有.o文件的集合体,从这里可以获取dyld的路径,然后加载dyld。

  • dyld是指苹果的动态链接器,加载dyld后,就会去初始化运行环境,开启缓存策略,加载依赖库,并且会调用每一个依赖库的初始化方法,包括RunTime也是在这里被初始化的,当所有的依赖库都被初始化完成后,RunTime会对项目中所有的类进行类初始化,调用所有的+load()方法,最后dyld会返回main函数地址,然后main函数会被调用。

  • 知晓上述的流程后,我们就明白为什么优化启动速度,要去减少动态库加载,要少用+load(),理论明白了之后,我们就要看看具体怎么做了。

  • 动态库是指可以共享的代码文件、资源文件、头文件等的打包集合体。在Xcode->Targets->General->Link Binary With Libraries可以检查自己的库,

  • 减少+load()的使用,将里面的内容放到渲染结束后去做,或者用+initialize()代替。+load()方法在main()调用前就会调用,而+initialize()方法是在类第一次收到消息后,才会调用,两者的区别可以参考这里

    Main函数调用前

    Main函数调用后

2.具体优化方法

(1)减少+load()的使用

使用+initialize()的方法代替+load(),注意把逻辑移动到+initialize()时,要注意避免+initialize()的重复调用问题,可以使用dispatch_once()让逻辑只执行一次。

(2)对多个动态库进行合并

苹果公司建议使用更少的动态库,并且建议在使用动态库的数量较多时,尽量将多个动态库进行合并。数量上,苹果公司最多可以支持6个非系统动态库合并为一个。

(3)优化类、方法、全局变量

减少加载启动后不会去使用的类或方法;控制C++全局变量的数量

(4)功能级别的启动优化

main()开始执行后到首屏渲染完成前,只处理首屏相关的业务,其他的都放到首屏渲染完成后去做。

(5)方法级别的启动优化

首先检查首屏渲染完成前主线程上的耗时操作,将没必要的操作滞后或异步。通常耗时操作有:加载、编辑、存储图片和文件等资源。

3. 查看耗时

(1)查看Main()调用前花费的总时间

在Product->Scheme->Edit Scheme->Run->Arguments->Environment Variables->DYLD_PRINT_STATISTICS设置为YES,就可以在控制台中查看main函数执行前总共花费的多长时间。

设置环境变量.png

控制台会输出pre-main的总时间.png

(2)查看加载了多少动态库

在Product->Scheme->Edit Scheme->Run->Diagnostics->Logging->勾选Dynamic Library Loads,就可以在控制台中查看本项目中加载的所有动态库(包括系统的和自己的)。


image.png
(3)查看Main函数启动后的耗时

main函数调用后的耗时,可以使用一些工具来监控,有一种非常笨但是很实用的方法,就是通过打点,在didFinishLaunchingWithOptions开始前打一个点,在App显示完成第一个界面再打一个点,计算两个点之间的耗时,就可以知道main函数调用后到界面显示出来的耗时了,但是这样只能笼统的知道总的耗时,并不能准确的知道时间花在了哪里。

如果想用这个打点法的话,推荐一个打点工具BLStopwatch

如果想准确知道时间都花在了哪里,推荐使用下面两种方法。

4. 监控App启动耗时,精准找出时间都花在了哪里,方便逐一优化

准确监控方法有两种:
  1. 定时抓取主线程上的方法调用堆栈,计算一段时间里各个方法的耗时。Xcode自带的Time Profiler就是用的这种方法。

  2. 对objc_msgSend方法进行hook来掌握所有方法的执行耗时。

根据这两种方法,分别实现两个工具,来监控耗时

由于能力有限,我只根据第一种方法做出来一个计算某个线程的耗时工具,放在了这里BSMonitorTimeTool,大致思路如下:

(1). 通过定时器,每隔0.01s,获取一次主线程的函数堆栈,将函数名称、函数地址、函数耗时模型化为TimeModel,保存在callStackDict中,其中key为函数地址,value为TimeModel

(2). 定时执行的回调中,每次都判断函数地址是否存在,如果已经存在此函数地址,就讲对应的TimeModel中的耗时增加0.01s;如果不存在此函数地址,就初始化一个TimeModel,并将时间设置为0.01s。

(3). 当主界面显示完成之后,输出此callStackDict,即可查看主线程中每个方法的耗时

5. 欢迎大家指正错误,希望能够共同进步

本文章是参考了很多大佬的文章,欢迎各位前去膜拜

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,222评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,455评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,720评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,568评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,696评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,879评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,028评论 3 409
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,773评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,220评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,550评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,697评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,360评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,002评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,782评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,010评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,433评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,587评论 2 350

推荐阅读更多精彩内容