1 启动类型
冷启动(Cold Launch)
第一次打开 App 或者 App 已经被完全关闭后再次启动时发生的情况。在冷启动过程中,App 需要重新初始化,并加载必要的资源和数据。
热启动(Warm Launch)
App 已经在后台运行,再次启动 App 时发生的情况。App 在后台的存活时间,就是 App 能够执行热启动的最大时间间隔。
2 启动流程
Apple 官方的《WWDC Optimizing App Startup Time》 将 iOS 应用的启动分为 pre-main 阶段和 main 两个阶段。
2.2 pre-main 阶段
在 “pre-main” 阶段,iOS 应用程序的启动过程的关键步骤:
- 加载解析 info.plist 文件
系统会加载并解析应用程序的 Info.plist 文件,该文件包含了应用程序的配置信息,如应用程序的图标、启动图、权限要求等。
- 创建沙盒
在 iOS 8 及其之后的版本,每次启动应用程序时都会生成一个新的沙盒路径,用于存储应用程序的数据、设置文件等。这个沙盒是应用程序的私有存储空间。
- 权限检查
根据 Info.plist 文件中配置的权限要求,系统会检查应用程序所申请的各种权限状态,如访问相机、定位、通知等权限。
- 加载 Mach-O 文件并运行动态连接器(**Dynamic Link Editor,简称** dyld**)**
- dyld 寻找合适的 CPU 运行环境;
- dyld 加载程序依赖的动态库(系统动态库和第三方动态库)和源代码文件(.h/.m)编译生成目标文件(.o),并对这些动态库进行连接;
- 加载所有方法。在这个阶段,初始化 Runtime 并完成 Objective-C 的内存布局,包括加载所有类、方法等;
- 加载 C 函数;
- 加载类扩展和 Category。在这个阶段,Runtime 会对所有类结构进行初始化,包括加载类的扩展和 category;
- 加载 C++ 静态函数和执行 Objective-C 类的 +load 方法;
- 最后,调用 main 函数,应用程序的主逻辑开始执行。
Mach-O(Mach Object)是 macOS 和 iOS 等苹果操作系统上使用的一种可执行文件格式,类似于 Windows 上的 PE(Portable Executable)文件格式。
在编译过程中,源代码经过编译器的处理生成目标文件(.o 文件),然后链接器将这些目标文件以及所需的动态库链接成最终的 Mach-O 可执行文件。这个过程通常包括符号解析、重定位、符号表生成等步骤,最终生成一个完整的可执行文件,供操作系统加载和执行。
影响启动时间因素:
- 动态库的数量
- OC类的数量,Category的数量
- C/C++ 中的构造器函数
- C++ 静态对象
- OC类的 +load 方法
优化方案:
- 减少动态库加载,
- 移除不必要的动态库
- 合并多个动态库成一个动态库
- 减少无用的类或者方法
- 清理项目中无用的类和方法,包括无用的 Category
- 清理无用的静态变量,可以通过 AppCode 代码扫描
- 减少 C++ 静态对象的数量
- 检查 +load() 方法
- 规避启动时的非必要操作,可以放到首屏渲染完成后再执行
- 使用
+initialize()
方法替换
打印App的启动时间
在控制台打印对应的时间日志
Project→ Scheme → Edit Scheme → Run → Arguments → Environment Variables
设置参数 DYLD_PRINT_STATISTICS = 1
,打印详细信息 DYLD_PRINT_STATISTICS_DETAILS = 1
1 | yaml复制代码Total pre-main time: 770.45 milliseconds (100.0%) |
- dylib loading time:加载动态库,包括系统动态库和第三方动态库。
- rebase/binding time:地址绑定。
- ObjC setup time:注册 Objc 类,完成 Objc 类名与类的关系映射、维护 SEL-IMP 的关系映射,将 Protocol/Category 等注入宿主类方法列表中等。
- initializer time:调用 Objc 类的 +load() 方法、调用 C++ 类的构造函数。
2.3 main 阶段
“main” 阶段可拆分为两个小阶段。
第一阶段:从 main
函数执行开始到应用程序代理 didFinishLaunchingWithOptions
函数执行完成。
影响启动时间因素:
- 执行
main
函数的耗时 - 执行
applicationWillFinishLaunching
函数的耗时
优化方案:
- 尽量不要在
main
函数中添加耗时的任务 - 按需加载,仅加载 App 启动和首屏渲染必要业务,对于非必要的业务移到首屏渲染完成后加载
第二阶段:首屏渲染阶段。
影响启动时间因素:
- 如加载、编辑、存储图片、文件等资源
- 运行时方法替换会带来时间消耗,如 +load() 执行有4毫秒的延迟
优化方案:
- 采用缓存策略,预加载数据减少用户等待的时间。
- 如果首页的内容实时性不是特别强的,可以通过接口获取最新数据(并存储到磁盘),如果本次数据与已经渲染的数据不一致,则刷新用户界面。
- 延迟请求相关接口
- 版本升级提醒或者拉取全局配置信息,如果是单独的接口,则可以延迟请求数据。全局配置信息可以在每次请求到远程最新数据时缓存到本地磁盘,应用启动后优先读取本地的数据,并且延迟请求远程数据。
- 拆分接或合并接口请求
- 如果首页的数据是分多个模块(或微服务)且模块之间是弱相关的,把所有数据放在一个接口会增加后端查询量,导致接口响应过慢。可以按照逻辑划分,在子线程发出请求,数据响应后在子线程完成相关预处理,再回到主线程更新UI;哪个请求先回来就先渲染哪个模块的数据。
- 如果首页的数据相关性很大,可以将有依赖的接口合并为一个接口,以减少接口请求的次数。
- 延迟初始化三方服务
- 第三方服务SDK可以放在子线程完成行初始化,必须放在主线程初始化的,可以延迟几秒再初始化。
- 使用骨架屏等方案
- 在网络请求过程中展示骨架屏会给用户一种数据即将展示出来的感觉。
本文转载自: 掘金