背景
iOS发动优化是指在iOS设备上提高运用程序的发动速度和呼应性能的一系列技能和办法。跟着移动设备的遍及和运用程序的功能复杂化,用户对运用程序的呼应速度要求也越来越高,因而发动优化成为了开发者们重视的重要领域。
提到发动,这其实是一个非常重要的用户体会场景,前期有些博主喜爱拿安卓和苹果打开某个运用的时刻来比照设备之间的差异,也经过这个发动某个软件的时刻这个指标判别两者体系的优劣。
当然关于用户来说在某些场景也是影响很大,拿我之前遇到一个场景来说,当时和多年未见的朋友碰头,说好了请对方吃饭,到了付钱的时分,我的朋友也抢着去付钱,在这个时分,我预备点开微信app 发动的时分,忽然卡住了,然后终究让朋友抢先一步付了钱,这是不是很尴尬。
话说回来,苹果关于发动时刻过长会有一个看门狗机制,假如发动时刻过长,看门狗将终止它,这对用户来说也是致命的。
通常状况下,假如运用程序是他们惯例工作流程的一部分,用户一天会多次发动该运用程序,而较长的发动时刻会导致履行任务的推迟。
当用户点击主屏幕上的运用程序图标时,iOS 会在将控制权移交给运用程序进程之前预备发动运用程序。该运用程序然后运转代码以预备将其 UI 制作到屏幕上。即便在运用的 UI 可见之后,运用可能仍在预备内容或用终究控件替换空隙界面。这些过程中的每一个都会影呼运用程序的总感知发动时刻。
发动流程
下面就先从发动的整个流程来看看发动究竟都做了哪些工作
从几个大的阶段来划分
能够看出首要分为以下几个大阶段
1.加载Mach-O阶段
2.dyld 阶段
3.main 之后阶段:加载发动项到终究的viewDidAppear调用加载第一帧。
从dyld和runtime视点来划分
假如结合dyld 和runtime 这两个中心模块来划分,发动阶段首要由他们两者协作完结。
具体阶段过程
那么整体的流程能够梳理为以下过程
一.内核态加载mach-o文件和可履行文件
关于mach-o文件
Mach-O文件简介
- Mach object的缩写,是Mac、iOS上用于存储程序、库的规范格局 ,Mach-O文件是一种叫法,就像以.text结束的文件,被叫做为text文件
常见的Mach-O文件有:
- MH_OBJECT:方针文件(.o)、静态库文件(.a) 静态库其实便是N个.o兼并在一起
- MH_EXECUTE:可履行文件.app/xx
- MH_DYLIB:动态库文件.dylib或.framework/xx
- MH_DYLINKER:动态链接编辑器/usr/lib/dyld
- MH_DSYM:存储着二进制文件符号信息的文件 .dSYM/Contents/Resources/DWARF/xx(常用于剖析APP的溃散信息)
可履行文件:
- 平时编写的代码终究会被编译成为一个Mach-O格局的文件
- 开发过程中所用到的动态库(比方:UIKit、Foundation) 依靠信息也会存储在可履行文件中
二.dyld 阶段
简略介绍下dyld是什么
“dyld” 是苹果操作体系中的一个重要组件,它是动态链接器(dynamic linker)的缩写。动态链接器是操作体系加载和链接可履行文件所需的同享库的中心组件之一。
dyld 的首要功能是在程序发动时加载和链接程序所依靠的同享库,并将其映射到进程的内存空间中。它担任解析和处理同享库之间的符号依靠关系,以及处理运转时的符号重定位。
具体来说,dyld 的工作流程如下:
- 加载:当一个可履行文件(如运用程序)发动时,dyld 担任加载可履行文件和它所依靠的同享库到内存中。
- 符号解析:dyld 解析可履行文件和同享库中的符号引证,找到对应的符号界说,以便正确地链接和运转程序。
- 符号重定位:在加载和链接过程中,dyld 会处理符号重定位,将程序中的符号引证指向正确的地址。
- 发动程序:完结加载和链接后,dyld 将控制权转交给程序的入口点,使其开始履行。
dyld 的存在使得运用程序能够动态地加载和链接同享库,然后完结了代码的同享和重用。这也是为什么在 iOS 开发中,咱们能够运用各种体系提供的结构和库来构建运用程序。dyld 是苹果操作体系中担任动态加载和链接同享库的组件,它在运用程序发动时发挥着要害的效果,保证程序能够正确地加载和履行所需的代码和库。
关于动态库:
- 程序运转时由体系动态加载到内存,而不是复制,供程序调用。
- 体系只加载一次,多个程序共用,节省内存。因而,编译内容更小,而且由于动态库是需求时才被引证,所以更快。 简略知道:体系的UIKit结构终究被dyld以动态库的形式加载到内存 !
dyld阶段所做的工作
load dylibs > rebase bind > objc(Notify ObjC Runtime) > initializers
1.load dylibs
装载app的可履行文件,同时会递归加载一切依靠的动态库。
-
Parse image(解析图画):在这个过程中,dyld 解析可履行文件或同享库的二进制格局。它会读取可履行文件的头部和段(segments),以及同享库的符号表和重定位信息等。经过解析图画,dyld 能够了解文件的结构、符号引证和重定位需求。
-
Map image(映射图画):在这一阶段,dyld 将可履行文件或同享库映射到进程的内存空间中。它会分配适当的内存区域,并将二进制文件的内容加载到这些内存区域中。经过映射图画,dyld 将文件中的代码、数据和资源加载到内存,为后续的重定位和符号绑定做预备。
当dyld加载完可履行文件 和动态库 之后通知runtime进行下一步操作。
2.Rebase + bind
-
Rebase image(重定位图画):在此过程中,dyld 处理可履行文件和同享库中的重定位信息。重定位信息描绘了代码和数据的方位相关于内存中的基地址的偏移量。dyld 依据基地址和重定位信息来计算并更新代码和数据的必定地址,以保证它们在内存中正确定位。
-
Bind image(符号绑定图画):在这个阶段,dyld 解析可履行文件和同享库中的符号引证,并将它们绑定到相应的符号界说。符号绑定是将符号引证与符号界说相关联的过程,保证程序能够正确地拜访和履行所需的符号。经过符号绑定,dyld 保证程序能够正确链接并履行依靠的函数和变量。
3.objc(Notify ObjC Runtime)
- mapimages 对二进制文件内容解析处理。
- runtime在此处初始化,对class和category进行注册。
- 进行各种objc结构的初始化(objc 类被界说和注册)。
- 分类被插入到办法列表中。
- selector唯一性判别。
4.Initializers
- loadimages 调用 call_load_images 加载 类和 分类的 load办法
- 调用c++静态初始化器和__attribute(construct)修饰的函数
至此可履行文件和动态库的符号sel class protocol IMP 都已经按需加载到内存中了,被runtime办理终究,Dyld calls main()
三.进入main函数
-
接下来便是 UIApplicationMain 函数,相关的调用了,Appdelegate会顺次履行 对应的生命周期办法。
-
创建整个app的autoreleasepool,初始化初始window,app界面开始展。
-
指定rootviewcontroller,调用事务代码,完结各阶段事务。
-
main页面viewDidAppear 完结页面第一帧烘托。至此发动完结。
关于发动规范
苹果的规范
-
针对发动时刻的最佳规范 (400ms 是一个很好的方针)
-
最坏的状况 (不要超越20秒不然运用程序将被杀死)
关于优化计划
优化计划从各个阶段来考虑
1.加载mach-o阶段
重新排列函数符号方位,下降MACH-O文件载入内存时PageFault缺页中止频率 – 二进制重排
- 一种是抖音的计划二进制重排。(官方说会有百分之30提高,自己测验并没有太大提高。)
- 别的是苹果推出的pgo。(大概有百分之10左右的提高)
两者类似
原理
二进制重排实际上是在windows和linux上就存在的技能,旨在将发动用到的函数办法尽可能的放置在二进制文件加载的前面,并且是将函数符号地址接连的编译在一起,以削减Page Fault的次数和频率,加快发动速度。现在这项技能已经移植运用到了移动端app上。
操作体系为了处理安全问题和功率问题,抽象出了虚拟内存页的概念。内存都是分页拜访的。这儿的page指的便是内存页。(就像磁盘存储的最小单位 磁盘簇,大小是4k相同) MacOS 、linux (4K为一页) iOS(16K为一页)
PageFault便是缺页中止:当app调用一个办法,发现该办法没有在内存中,此刻操作体系就会立刻堵塞整个app进程,触发一个缺页中止。操作体系会从磁盘中读取这页数据到物理内存上 , 然后再将其映射到虚拟内存上 ( 假如当时内存已满 , 操作体系会经过置换页算法找一页数据进行掩盖, 这也是为什么开再多的运用也不会崩掉 , 但是之前开的运用再打开时 , 就重新发动了的根本原因 )。
假如,app发动时期需求调用 method1、method5和method6,这三个办法散布在page1、page2和page3上。每装载一个内存页page都会发生一次PageFault(缺页终端)。通常一个PageFault的处理时刻是0.1ms~1ms,取0.5ms计算。这三次处理PageFault时刻是 3 * 0.5ms = 1.5ms。
2.针对dyld阶段
-
削减动态库 兼并动态库 (定时整理不必要的动态库)
-
削减oc 类 分类 办法 sel(定时整理不必要的类 分类)
-
削减c++虚函数数量
-
swift尽量运用struct
3.针对objc 和 initialize 能够看做是 runtime 阶段
- 用+initialize办法和dispatch_once替代一切的__attribute__((constructor))、C++静态结构器、ObjC的+load
4.针对 main函数之后阶段
- 在不影响用户体会的前提下,尽可能将一些操作推迟,不要全部都放在finishLaunching办法中
- 发动任务的次序调整优化
- 下降初始视图的复杂性
- 按需加载
关于测验(建议在支撑的最慢设备上测验)
1.推荐2个github 东西
- github.com/ming1016/GC… (SMCallTrace)
- github.com/EmergeTools…
2.Xcode 和 instrument自带东西
- lldb调试东西设置 Edit scheme -> Run -> Arguments 中将环境变量 DYLD_PRINT_STATISTICS 设为 1统计结果会在打印窗口输出
- 经过instrument 的 launching
3.线上监控
主动埋点 分为pre-main 和 main 之后
经过苹果的metric
总结
以上基本涵盖了iOS 发动各个阶段的具体流程,以及优化计划,当然发动优化也是一个长期需求重视的稳定性指标,也要结合当时项目状况分步优化,经过优化>监控>优化 形成闭环,发现问题并处理问题,终究持续下去必定会有收益的。