iOS 启动优化篇

启动速度

用户从点击APP图标到完全看到APP内容的过程称为启动,如果启动耗时较长可能会影响用户的体验,所以启动速度优化就显得很有必要。

  • 最佳速度:400ms,这是刚好是启动动画的时间,这是app启动时间的最佳时间。业界建议启动时间保持在1.5s内比较合适。

  • 最慢速度:超过20s,则会被系统杀掉。

启动的分类

1、冷启动:系统里没有APP的进程缓存信息,例如重启手机或者更新APP后的首次启动APP,APP长时间不用系统清掉已有的进程缓存

2、热启动:系统里有APP的进程缓存信息,例如杀死APP后短时间内重启APP

3、回前台:APP退入后台再进入前台,APP进程从挂起到激活状态

一般只讨论1、2两种情况的启动优化。如何从代码层面计算启动速度?根据苹果官方文档的计算方式:进程创建时间到第一个CA::Transaction::commit()

启动流程

image.png

1、点击APP图标后,内核创建APP进程

2、将APP的Mach-O可执行文件mmap进虚拟内存,加载dyld程序,接下来调用_dyld_start函数开始程序的初始化

3、重启手机/更新APP会先创建启动闭包,然后根据启动闭包进行相关的初始化

4、将动态库mmap进虚拟内存,动态库数量太多则这里耗时会增加

5、对动态库和APP的Mach-O可执行文件做bind&rebase,主要耗时在 Page In,影响 Page In 数量的是 objc 的元数据

6、初始化 objc 的 runtime,如果有了闭包,由于闭包已经初始化了大部分,这里只会注册 sel 和装载 category

7、+load 和静态初始化被调用,除了方法本身耗时,这里还会引起大量 Page In

8、初始化 UIApplication,启动 Main Runloop

9、执行 will/didFinishLaunch,这里主要是业务代码耗时

10、Layout,viewDidLoad 和 Layoutsubviews 会在这里调用,Autolayout 太多会影响这部分时间

11、Display,drawRect 会调用

12、Prepare,图片解码发生在这一步

13、Commit,首帧渲染数据打包发给 RenderServer,启动结束

什么是PageIn?
启动的路径上会触发很多次 Page In,其实也比较容易理解,因为启动的会读写二进制中的很多内容。Page In 会占去启动耗时的很大一部分,我们来看看单个 Page In 的过程:

image.png

  • MMU 找到空闲的物理内存页面
  • 触发磁盘 IO,把数据读入物理内存
  • 如果是 TEXT 段的页,要进行解密
  • 对解密后的页,进行签名验证 (iOS 13 对这个过程进行了优化,Page In 的时候不需要解密了。)

启动优化

启动速度优化思路:

1、控制APP的可执行文件大小

2、控制动态库数量

3、控制Page In 次数

4、控制首帧渲染前业务逻辑相关耗时

5、控制首帧视图渲染耗时,即上面流程中的步骤10-12

1、尽可能减少动态库的引用

至于什么是动态库,什么叫静态库?
静态库:编译时链接,链接时完整地拷贝至可执行文件中, 被多个依赖多次使用就会有多份冗余拷贝. 动态库: 链接时不复制, 程序运行时由系统动态加载到内存, 供程序调用, 系统只加载一次, 多个程序共用, 节省内存. iOS 中的静态库与动态库

2、删除无用代码

Q: 无用代码会增加APP可执行文件的大小吗?
A: 项目工程中未使用到的无用代码最终会编译到APP的可执行文件中区,所以会导致APP可执行文件体积增加。当APP可执行文件变大时,会导致dyld加载可执行文件的耗时增加,即增加了启动时间。

如何做?

基于Mach-O可执行文件格式中的字段来确定类或者函数是否被使用,优点是实现简单,缺点是不够准确,只能做静态分析,动态调用代码无法查出来,所以删代码前需要二次确认,目前也有很多不错的实现方案。

采用业界开源方案:WBBlades 分析项目中的无用类,该工具支持OC和Swift的检测,且使用简单。

3、二进制重排

Q: 二进制重排为什么会加快启动速度?
A: 当APP进程访问一页虚拟内存page,而对应的物理内存不存在时,先触发缺页中断(Page Fault)阻塞当前进程,然后加载数据到对应物理内存(Release版本还要对加载的数据进行签名),所以缺页中断还是比较耗时的。假设APP启动时调用100个函数,这100个函数如果分布在100个不同的内存页,那会产生100次缺页中断。如通过二进制重排将这100函数分布到50个或者更少的内存页中,缺页中断的次数减半,启动速度就提升了 。

获取启动阶段Page Fault的次数
打开Instruments,选择System Trace工具

image.png

重启手机(热启动情况下系统已经做了加载缓存,产生缺页中断大幅减少,所以最好重启手机),然后点击启动,待首屏出现后停止,如下图:
image.png

解决方案

获取启动阶段调用的函数符号然后编写order_file编译顺序文件然后在Build Settings -> Order File中配置一个后缀为order的文件路径是实现二进制重排的核心思路。

使用clang编译器静态插桩
静态插桩:在build settings->"Other C Flags"中添加"-fsanitize-coverage=func,trace-pc-guard"。如过项目中有 Swift 代码,还需要在 "Other Swift Flags"中加入"-sanitize-coverage=fun""-sanitize=undefined",如下:

image.png

image.png

静态插桩脚本 获取启动阶段符号表的使用步骤:

1、在Podfile中添加如下代码:

pod 'YCSymbolTracker'
post_install do |installer|
    require './Pods/YCSymbolTracker/YCSymbolTracker/symbol_tracker.rb'
    symbol_tracker(installer)
end

然后执行pod install

2、再首帧完成渲染前调用如下代码:

    // 首帧渲染完成后调用此方法,一般在跟控制器的viewDidAppear方法中调用即可
    static func runAfterFirstFrameRendered(){
        ....省略的业务代码
        
        // 首帧渲染前调用监控代码
        let filePath = NSTemporaryDirectory().appending("/demo.order")
        YCSymbolTracker.exportSymbols(filePath: filePath)
    }

3、关机然后打开APP,获取启动阶段的符号表


image.png
4、首帧渲染前的业务逻辑优化

这部分代表了main()函数之后的时间,即从didFinishLaunchingWithOptions()开始到根控制器viewDidAppear函数结束主线程耗时的优化

解决方案:

利用Xcode自带的Instruments工具APP Launch分析启动耗时,找出耗时严重的函数调用然后进行优化。该工具会追踪应用启动后5秒内的所有线程的耗时,自带Time Profiler和应用进程的System Trace两个看板,如下:


image.png

接下来利用System Trace功能对APP启动后的业务逻辑进行耗时分析,点击System Trace看板(也就是上面FilmoraGo)左边的右箭头,选择Main Thread,接下来就可以看到启动阶段各个函数的耗时了。



这里分析比如我们可以把一些类似加载字体,或者一些不影响业务的操作延后或者放到子线程中执行。

参考文章

抖音品质建设 - iOS启动优化《原理篇》

抖音品质建设 - iOS启动优化《实战篇》

基于mach-o+反汇编的无用类检测

iOS静态库&动态库依赖探索

IOS优化篇之启动速度优化(一)

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

推荐阅读更多精彩内容