聊聊iOS的启动优化怎么做?

首先我们需要知道iOS在启动会发生什么?

image.png

启动优化时间段

在苹果官方,将app的启动时间分为两个阶段

T1: pre-main 阶段,即main()函数之前,操作系统加载app可执行文件到内存中,然后执行一系列的加载&链接工作,最后执行到App的main() 函数

即我们常说的加载Mach-O文件的过程

T2: main()函数之后,即从main()开始,到appDelegate的didFinishLaunchingWithOptions方法执行完毕前这段时间,主要是构建第一个界面,并完成渲染。

而,从用户点击App图标开始到用户能看到App主界面内容为止这个过程,即T1+T2。

image.png

接下来简单聊聊分别针对这两个时间段我们能做哪些优化

pre-main 阶段优化

通过上图,我们可以看到,pre-main阶段,app主要做的是dyld加载操作, 其次代码区主要在做init和+load函数的事情
首先来看下加载Mach-O.
我们可以通过xcode打印时间来查看,哪些时间是占用比较多的

检测方法
获得 main() 方法执行前的耗时比较简单,通过 Xcode 自带的测量方法既可以。将 Xcode 中 Product -> Scheme -> Edit scheme -> Run -> Environment Variables 将环境变量 DYLD_PRINT_STATISTICS 或 DYLD_PRINT_STATISTICS_DETAILS 设为 1 即可获得执行每项耗时:

// example 
// DYLD_PRINT_STATISTICS
Total pre-main time: 383.50 milliseconds (100.0%)
         dylib loading time: 254.02 milliseconds (66.2%)
        rebase/binding time:  20.88 milliseconds (5.4%)
            ObjC setup time:  29.33 milliseconds (7.6%)
           initializer time:  79.15 milliseconds (20.6%)
           slowest intializers :
             libSystem.B.dylib :   8.06 milliseconds (2.1%)
    libMainThreadChecker.dylib :  22.19 milliseconds (5.7%)
                  AFNetworking :  11.66 milliseconds (3.0%)
                  TestDemo :  38.19 milliseconds (9.9%)

// DYLD_PRINT_STATISTICS_DETAILS
  total time: 614.71 milliseconds (100.0%)
  total images loaded:  401 (380 from dyld shared cache)
  total segments mapped: 77, into 1785 pages with 252 pages pre-fetched
  total images loading time: 337.21 milliseconds (54.8%)
  total load time in ObjC:  12.81 milliseconds (2.0%)
  total debugger pause time: 307.99 milliseconds (50.1%)
  total dtrace DOF registration time:   0.07 milliseconds (0.0%)
  total rebase fixups:  152,438
  total rebase fixups time:   2.23 milliseconds (0.3%)
  total binding fixups: 496,288
  total binding fixups time: 218.03 milliseconds (35.4%)
  total weak binding fixups time:   0.75 milliseconds (0.1%)
  total redo shared cached bindings time: 221.37 milliseconds (36.0%)
  total bindings lazily fixed up: 0 of 0
  total time in initializers and ObjC +load:  43.56 milliseconds (7.0%)
                         libSystem.B.dylib :   3.67 milliseconds (0.5%)
               libBacktraceRecording.dylib :   3.41 milliseconds (0.5%)
                libMainThreadChecker.dylib :  21.19 milliseconds (3.4%)
                              AFNetworking :  10.89 milliseconds (1.7%)
                              TestDemo :   2.37 milliseconds (0.3%)
total symbol trie searches:    1267474
total symbol table binary searches:    0
total images defining weak symbols:  34
total images using weak symbols:  97

看完时间后我们再来具体看看做了些什么? 以及我们在这个过程怎么优化点.

加载可执行文件
加载 Mach-O 格式文件,既 App 中所有类编译后生成的格式为 .o 的目标文件集合。

dyld 加载 dylib 会完成如下步骤:

分析 App 依赖的所有 dylib。
找到 dylib 对应的 Mach-O 文件。
打开、读取这些 Mach-O 文件,并验证其有效性。
在系统内核中注册代码签名。
对 dylib 的每一个 segment 调用 mmap()。

系统依赖的动态库由于被优化过,可以较快的加载完成,而开发者引入的动态库需要耗时较久。
优化点:
1.减少动态库数量可以加减少启动闭包创建和加载动态库阶段的耗时,官方建议动态库数量小于 6 个。

  1. 推荐的方式是动态库转静态库,因为还能额外减少包大小。另外一个方式是合并动态库,但实践下来可操作性不大。最后一点要提的是,不要链接那些用不到的库(包括系统),因为会拖慢创建闭包的速度。
Rebase和Bind操作

由于使用了ASLR 技术,在 dylib 加载过程中,需要计算指针偏移得到正确的资源地址。 Rebase 将镜像读入内存,修正镜像内部的指针,消耗 IO 性能;Bind 查询符号表,进行外部镜像的绑定,需要大量 CPU 计算。
优化点:
下线代码可以减少 Rebase & Bind & Runtime 初始化的耗时。那么如何找到用不到的代码,然后把这些代码下线呢?可以分为静态扫描和线上统计两种方式。我们可以定期扫描一下项目里面的无用方法,这里需要将方法找出来,然后推进大家负责的模块,去删除.

Objc setup, Initializers, 加载资源文件

进行 Objc 的初始化,包括注册 Objc 类、检测 selector 唯一性、插入分类方法等。

往应用的堆栈中写入内容,包括执行 +load 方法、调用 C/C++ 中的构造器函数(用 attribute((constructor)) 修饰的函数)、创建非基本类型的 C++ 静态全局变量等。

优化点:
+load 迁移
+load 除了方法本身的耗时,还会引起大量 Page In,另外 +load 的存在对 App 稳定性也是冲击,因为 Crash 了捕获不到。

合并功能类似的类和扩展(Category)
由于Category的实现原理,和ObjC的动态绑定有很强的关系,所以实际上类的扩展是比较占用启动时间的。尽量合并一些扩展,会对启动有一定的优化作用。不过个人认为也不能因为它占用启动时间而去逃避使用扩展,毕竟程序员的时间比CPU的时间值钱,这里只是强调要合并一些在工程、架构上没有太大意义的扩展。

通过减少IO操作量级优化 - 压缩资源图片
压缩图片为什么能加快启动速度呢?因为启动的时候大大小小的图片加载个十来二十个是很正常的,图片小了,IO操作量就小了,启动当然就会快了。

其次因为我的项目是OC-Swift的,由于OC有运行时,会比较耽误时间(各种runtime消息转发), 我们在把OC的类,尽量重写到Swift去,因为Swift静态语言的原因,编译完成就可以检测出没有调用的函数,优化删除后是可以减少二进制文件大小的,从而直接从体积上减少了加载时间. 建议大家在发版后的空闲时期,可以来做一些重构的工作

mian时间段的优化

main阶段的启动优化, 在main()函数之后的didFinishLaunchingWithOptions方法里执行了各种业务,有很多业务不是一定要在这里执行,我们可以延迟加载,防止影响启动时间。
在didFinishLaunchingWithOptions方法里我们一般做一下逻辑:

初始化第三方sdk
配置App运行需要的环境
自己的一些工具类的初始化 等等

Application Initializaiton

这个阶段主要是生命周期方法的回调,也正是开发者最熟悉的部分。

调用 UIApplicationDelegate 的 App 生命周期方法:

  application:willFinishLaunchingWithOptions: 
  application:didFinishLaunchingWithOptions:

在这个阶段,开发者可以做的优化:

推迟和启动时无关的工作
Senens 之间共享资源

Fisrt Frame Render

这个阶段主要做了创建、布局和绘制视图的工作,并把准备好的第一帧提交给渲染层渲染。会频繁调用以下几个函数:

 loadView
 viewDidLoad 
 layoutSubviews

我们在闪屏期间需要多关注首页的这些方法加载时间,提前加载,或者异步处理耗时操作,从而来优化启动时间

最后, 聊一下怎么检测你的优化成功

使用 Instruments 分析和优化 App 启动过程

当知道如何优化之后,我们需要针对我们的启动过程进行分析。Xcode 11 的 Instruments 为此新增了一个 App launch 模板,让开发者可以更好的分析自己 App 的启动速度。

image

运行后可以看到各个阶段的具体时间,根据数据进行优化,还能看到耗时的函数调用。

image

以上是自己在工作中总结的一些方法,希望对大家有用.

参考资料:

深入探索 iOS 启动速度优化

https://juejin.cn/post/6844904127068110862

高德 APP 启动耗时剖析与优化实践(iOS 篇)

https://www.infoq.cn/article/xjb3cysclphv5sh5923q

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

https://mp.weixin.qq.com/s/3-Sbqe9gxdV6eI1f435BDg

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

https://mp.weixin.qq.com/s/ekXfFu4-rmZpHwzFuKiLXw

iOS 优化篇 - 启动优化之Clang插桩实现二进制重排

https://mp.weixin.qq.com/s/UlMAvuLuTcWgd3qkEAHYMA

今日头条 iOS 客户端启动速度优化

https://juejin.cn/post/6844903649416577037

马蜂窝 iOS App 启动治理:回归用户体验

https://juejin.cn/post/6844903841410842638

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

推荐阅读更多精彩内容