贝壳找房 iOS 冷启动优化实践
如果无法正常显示,请先停止浏览器的去广告插件。
1. ⻉壳找房 iOS 冷启动优化实践
陈旭
⻉壳找房 资深工程师
2.
3. 优化效果展示
4. 1. 启动优化常⻅误区
目 录
CONTENTS
2. 优化思路分析
3. 主要优化方案及实践
4. 规范与监控
5. 总结与展望
5. 启动优化常⻅误区
6. 冷启动测试标准
01
重启手机,并静置 2-3 分钟,
或杀进程后静置 10 分钟以上
03
关闭 iCloud 或者保持使用同一
个 iCloud 账户
02
打开⻜行模式或者
mock 网络环境
04
使用 release 版本
7. 冷启动优化的本质
首要目标是只做必要的事、不做重复计算
只做现在必须做的事,不过早优化
资源换时间(空间换时间、时间换时间)
算法、策略等内部优化
充分利用 CPU 多核性能,最短时间执行完毕
8. 冷启动优化的常⻅误区
耗时操作简单放到子线程就可以了
任务延迟执行固定时间
任务延后到首页显示之后就完成目标了
启动阶段 CPU 占用过高会有问题
9. 优化思路分析
10. iOS冷启动阶段划分
• System Interface : 加载主二进制、启动 dyld、加载动态库以及 libSystem 初始化
• Runtime Init : 执行 +load 和 static initializer 初始化函数等
• 其他:实例化 UIApplication 和 UIApplicationDelegate、处理生命周期回调、首帧
渲染直到首⻚渲染完成
11. 优化可行性分析
System Interface
Application Init
Runtime Init
• 减少或延迟各种
SDK 的初始化
• 避免在+load 里操
作、延迟加载+load
及减少 static
initializer 等
• 避免链接不使用的框
架
• 减少动态库的加载
• 减少 OC 类
• 分类的个数等
Initial Frame Render Extended
• 减少视图层级和视图数量
• 懒加载 View
• 变 AutoLayout 为手动
frame 布局等 • 去掉 viewDidLoad 和
viewWillAppear 中不必
要的逻辑,少做事不做事
12. ⻉壳 iOS 冷启动阶段划分
优化前
优化后
13. 优化成果
• 以 B 端核心 App Link 为例,该 App 集成了二手、新房、租赁、
装修、直播、培训、IM 、VR 、IoT 等十几个业务
• 数百万行代码,近 200 个组件,Native ⻚面超过 500 个,
Flutter ⻚面超过 350 个,H5 ⻚面超过 900 个,业务和功能多,
复杂度高,优化难度极高
收益
u 优化了启动速度,提升了经纪
人的工作效率和体验
u 实现了生命周期的解耦,减少
了沟通成本和对平台的打扰
u 沉淀了通用的冷启动优化的技
术项,方便推广到其他 App
u 成果可以持续得到巩固,从机
制上避免了业务方对冷启动的
干扰
14. 主要优化方案
15. 思路
• 大的指导方针是延迟、内部优化、懒加载、异步等
• 以最小集为例,谁都觉得自己重要,但哪些是启动过程中真正不可缺少的,值得我们
思考
• 按最小集进行延迟,非最小集的任务项全部延迟,如何低成本且保持⻓效机制
• 启动快了,还要保证首⻚渲染完成后不会卡顿
• 针对各个技术点以及各个任务项需要进行内部优化
16. ⻉壳 B 端 App 架构
17. 主要方案
优化包括两部分,main()函数之前和 main()函数之后,简称 Premain 和 Postmain 。
Postmain
Premain
动态库懒加载
•
•
•
•
如何实现
如何手动加载
何时加载
注意事项
编译期消除 I/O Static Initializer
• 如何配置
• 如何实现 •
•
•
•
背景
扫描
分发
修改
无用代码删除
框架优化
• 最小集
• 静态扫描
• 动态覆盖率检查 • 生命周期延迟
• 启动器和任务
• 0PV 检测
编排
首⻚渲染优化
• 资源预加载
• 数据预加载
• 布局预加载
18. 1.1 最小集
根据⻉壳 App 的现状梳理出的最小集
• Crash 监控必须最早
• 网络配置不可或缺
• attach window、底部
tabbar 、首⻚展示与
UI 显示相关
• 日志、埋点等看耗时评
估,可以在启动阶段缓
存,启动完再写入
19. 1.2 框架优化-生命周期延迟
任务
本身
任务
调度
耗时
线程
切换
并发对
不⻬
任务项
延迟到
首页渲
染完成
之后
前期思路: 转换思路:
一项一项往后挪 把除了最小集之外
的任务项打包延迟
由于任务项之间
的依赖,要延迟
就要做到解耦,
效率非常低
冷启动加快,打
散延迟的任务
项,按照封装过
的生命周期进行
填空
20. 1.2 框架优化-生命周期延迟
21. 1.3 启动器
22. 1.4 任务编排
23. 1.5 线程管理
iOS 的 GCD 管理线程很方便,但为了进行统一的管控,我们根据优先级和分
类创建了 3 个线程队列:
主线程队列
管控所有UI操作及
必须在主线程执行
的高优和低优任务
后台线程队列
管控可以并行执行
的任务项
闲时线程队列
管控可以闲时执行的
任务
24. 2. 首⻚渲染优化
02
首页、底部tab的图片等可以提前预加载,
01
首页没有使用 Flutter
[UIImage imageNamed:]
04
拆细粒度,微小变化时局部刷新,不
03
要全局刷新
首页数据缓存提前加
载、首页布局预加载
05
减少页面层级,不需要显示的
view 进行懒加载
25. 3. 动态库懒加载
如何查看app里的动态库
otool -L /Users/chenxu/Desktop/XXX.app/XXX
动态库优点
•
•
•
资源隔离支持重命名
可按需加载
提高本地编译速度等
动态库懒加载
只打包进app, 不参与链接,业
务调用的时候手动加载
动态库缺点
•
影响启动性能
26. 3. 动态库懒加载
配置
podspec里添加spec.weak_frameworks = 'XXX'
运行时手动加载
保证Link Binary With Libraries 和 Other Linker Flags 没有链接对应的动态库。
方式一 方式二
使用-[NSBundle loadAndReturnError:] ,
优点是可以访问资源 直接使用dlopen()
27. 3. 动态库懒加载
调用时机
业务之间通过 Router 调用,把逻辑集
中到 Router 内,调用的时候懒加载
哪些动态库适合懒加载
依赖少的业务 bundle
注意事项
n 需要在 Other Linker Flags 里添加 -
undefined dynamic_lookup 指令
n Strip Style 设置为 Non-Global
Symbol
n 动态库符号缺失检查,nm –um 命令
获取动态库依赖的外部符号, nm –
gm 获取 APP 依赖的所有符号, 前者
不在后者中,则说明缺失
28. 4. 编译期消除I/O
1. 选中 target , 在 Build
Phases 中点+号创建新的
New Run Script Phase,添
加脚本
2. 编译期脚本读取 plist 生成
python 的字典和数组,并转
为 OC 字典和数组字面量
3.字符串拼接,生成读取 plist
到内存的方法
29. 5. static initializer治理
背景
扫描原理
C++ 全局常量必须保证在 main 函数之前初始化完毕,从而增加耗时
mod_init_func 的运行时机晚于 +load,可以在运行时 hook
分发 通过遍历 linkmap 中的 Symbols 能找到扫描的符号所属的文件号,而对应文件
号在 Object files 是能找到所属组件和文件名,进而能找到对应负责人
治理 __attribute((constructor)) 和 C++ 全局常量懒加载
30. 6. 无用代码删除
0 PV扫描
静态分析
1. __objc_class_list &
__objc_class_refs 做差集 1. hook VC 的生命周期,线上统
2. AppCode IDE 进行扫描 2. 统计本地 native ⻚面总和
计运行中的 VC
3. 0 PV ⻚面 = 总⻚面 - 浏览了
的⻚面
动态覆盖率检查
OC 的类首次初始化时,
+initialize 会被执行,系统会自
动标记
31. 7. 其他优化项
1 避免 router 调用,首⻚之前的 router 调用改为手动调用
2 首⻚使用真实的 VC,其他都是空 VC,等首⻚渲染完成,再把首⻚外的 VC 替换掉
3 手动调用homeVC.view来触发首⻚VC的viewDidLoad
4 synchronized(self) 实现单例的做法改为无锁的 dispatch_once , 防止并发访
问锁可能引起的耗时
5 避免直接 I/O 的方式读取 info.plist ,系统已经提供了内存读取的 API
32. 优化难点
1
2
4
启动任务项做延 +load C++ 动态库懒加载效果 每个研发人员的水
迟、异步可能会打 initializer ,涉及业 明显,但是改造成 平也参差不⻬,无
乱时序,有潜在的 务线众多,需要持 本较高,还需要注 意间可能就影响到
⻛险,需要对业务 续协调、推动和跟 意业务⻛险 冷启动优化,管控
做详细的梳理和⻛ 踪
险的评估
、
3
困难
33. 规范与监控
34. 规范
新增或修改任务要 首页渲染完成前不 不允许新增 +load
有足够的理由,必 允许监听系统生命 耗时方法
须经过严格的 周期
Code Review
不允许新增 C++ 新增动态库必须经 任务项相对上个版本
initializer 过评估 有 5ms 以上的增长
时,必须进行修改
35. 监控
线下
线上
建立标准,严格准入
开发
测试 线上
严控新增耗时
及时排查问题 及时预警和
快速定位
36. 总 结 与 展 望
37. 总结
深入优化方案
低成本高收益方案
01 生命周期延迟
02 +load 治理
03 动态库下线
04 二进制重排
05 首⻚预加载
20%
5%
01 动态库懒加载 02 static initializer 治理 03 编译期写入 I/O 1%
04 任务编排 1%
5%
4%
3%
3%
2%
⻉壳app里每个方案对启动速度的提升
38. 展望-自动归因
数据整理
• 每个版本的启动数据聚合、归一化
• 细化每个阶段的时间和每个任务项的时间
数据对比
• 不同版本的数据做diff
• 多维度收集启动之前的日志
自动归因
• 增量数据进行智能分析和归因
39. 展望-智能决策
40. Q&A
41.
42. 贝壳找房iOS冷启动优化实践
扫描二维码 提交议题反馈