持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第7天,点击查看活动详情
前言
- 评价一个App是不是一款出色的应用, 第一印象很重要.
- 这就要求我们必须把App的启动速度的优先级排的很高, 设想一个场景, *点击了App图标, 3秒过去了, 依然还卡在启动状态.*这是令用户难以接受的事.
- 要想解决问题, 首先要对问题有个全面的认识.
- 下面我们就App启动流程及启动优化实操层面, 做一个抛砖引玉的探讨, 如有错误, 请评论区指正, 先行谢过了:)
1. iOS启动流程分析
- App的启动可以分为两种
- 冷启动(Cold Launch): 从零开始, 点击App图标启动App
- 冷启动又可以概括为3大阶段
-
- dyld
-
- runtime
-
- main.m
- 热启动(Warm Launch): App之前已经启动好了, 在内存中, 后台运行, 再次点击App图标启动App
- App启动时间的优化, 主要是针对冷启动进行.
1.1 打印查看当前App的启动时间
- 可以通过添加XCode环境变量打印出App的启动耗时
- Edit scheme -> Run -> Arguments ,
DYLD_PRINT_STATISTICS
设置为1
.
1.2 App冷启动, 流程分析
- 启动时间是用户点击App图标, 到第一个界面展示的时间.
- 以main函数作为分水岭, 启动时间其实包含了两部分,
-
- main函数之前, 分析并加载动态库, 注册需要的类, Category中的方法也会注册到对应的类中, 执行必要的初始化
+load
方法
- main函数之前, 分析并加载动态库, 注册需要的类, Category中的方法也会注册到对应的类中, 执行必要的初始化
-
- main函数到第一个界面的
viewDidAppear
.
- main函数到第一个界面的
- 所以, 优化也是从两个方面进行的, 建议先优化第二部分, 因为绝大多数App的瓶颈在自己的代码里.
2. App冷启动, 启动优化策略
2.1 mian函数之前的启动优化
2.1.1 什么是dyld?
- dyld(dynamic link editor), 是Apple的动态连接器, 可以用来装载
Mach-O
文件(可执行文件、动态库等). - App冷启动时, dyld所做的事情有哪些?
- 装载App的可执行文件, 同时会递归加载所有依赖的动态库.
- 当dyld把可执行文件、动态库都装载完毕后, 会通知
runtime
进行下一步的处理.
2.1.2 dyld层面的优化方向
- 减少动态库的数量
- 合并动态库, 比如自己写的UI控件合并成自己的UIKit
- 确认动态库是
optional
还是required
. -
- 如果该Framework在当前App支持的所有iOS系统版本都存在,
-
- 那么就设为
required
, 否则就设为optional
, 因为option
会有些额外的检查.
- 那么就设为
2.1.3 App冷启动时runtime都做了哪些事?
- 调用
map_images
进行可执行文件内容的解析和处理 - 在
load_images
中调用call_load_methods
, 调用所有Class
和Category
的+load
方法 - 进行各种
objc
结构的初始化如注册Objc类、初始化类对象等 - 调用C++静态初始化器和
__attribute__((constructor))
修饰的函数
- 到此为止, 可执行文件和动态库中所有的符号(Class、Protocol、Selector、IMP等)都已经按格式成功加载到内存中, 被
runtime
所管理.
2.1.4 runtime加载层面的优化方向
- 合并Category, 如
UIView+Frame
和UIView+AutoLayout
合并成一个. - 将不必须在
+load
方法中做的事, 放到+initialize
中去做.
2.2 main函数之后的启动优化
- main函数开始执行到显示出第一个页面, 这段时间做了哪些事?
- 执行
didFinishLaunchingWithOptions
方法 - 初始化Window, 初始化基础ViewController
- 获取数据
- 展示给用户
- 减少创建线程, 线程不仅有创建时的时间开销, 还会消耗内存, 每个线程大约消耗1kb的内存空间.
-
- 线程创建的耗时, 区间范围在4-5毫秒, 创建线程后启动线程的耗时区间为5-100毫秒, 平均在29毫秒.
-
- 这是很大的时间开销, 若在应用启动时开启多个线程, 则尤为明显. 多次的上下文切换会带来开销.
-
- 在开发中避免滥用多线程.
- 合并或者删减不必要的类/分类/函数, 类越多, 函数越多, 启动越慢.
- 在设计师可接受的范围内, 尽量使用小的图片.
2.3 AppDelegate中的优化
- 从AppDelegate先入手优化
didFinishLaunchingWithOptions
、applicationDidBecomeActive
- 优化的核心思想就是, 能延时的延时, 不能延时的尽量放到后台去优化.
-
- 日志、统计等必须在App已启动就最先配置的事件, 仍留在
didFinishLaunchingWithOptions
里启动.
- 日志、统计等必须在App已启动就最先配置的事件, 仍留在
-
- 项目配置、环境配置、用户信息的初始化、推送、IM等事件, 这些功能在用户进入App首屏之前是必须要加载完的, 放到开屏广告页面的
viewDidAppear
里.
- 项目配置、环境配置、用户信息的初始化、推送、IM等事件, 这些功能在用户进入App首屏之前是必须要加载完的, 放到开屏广告页面的
-
- 其他SDK和配置事件, 由于启动时间不是必须的, 可以放在首屏的
viewDidAppear
方法里, 在这里不会影响启动时间.
- 其他SDK和配置事件, 由于启动时间不是必须的, 可以放在首屏的
-
- 每次用NSLog方式打印会隐式的创建一个
Calendar
, 因此需要删减启动时各业务的log, 或者仅针对内测版输出log.
- 每次用NSLog方式打印会隐式的创建一个
-
- 尽量不要在
didFinishLaunchingWithOptions
里面创建和开启多线程.
- 尽量不要在
3. 总结
- App的启动由
dyld
主导, 将可执行文件加载到内存, 顺便加载所有依赖的动态库 - 并由
runtiime
负责加载成objc
定义的结构 - 所有初始化工作结束后,
dyld
就会调用main
函数 - 接下来就是
UIApplictionMain
函数,AppDelegate
的
3.1 dyld阶段
- 减少动态库、合并一些动态库定期清理不必要的动态库
- 减少
Objc
类、分类的数量、减少Selector
数量, 定期清理不必要的类、分类 - 减少C++虚函数的数量
3.2 runtime阶段
- 用
+initialeze
方法和dispatch_once
取代所有的__attribute__((constructor))
、C++静态构造器、Objc的+load
方法
3.3 main函数阶段
- 在不影响用户体验的前提下, 尽可能将一些初始化操作延迟, 不要全部都放在
finishLaunching
方法中, 做到延迟加载, 按需加载.
发文不易, 喜欢点赞的人更有好运气? :), 定期更新+关注不迷路~
ps:欢迎加入笔者18年建立的研究iOS审核及前沿技术的三千人扣群:662339934,坑位有限,备注“掘金网友”可被群管通过~
© 版权声明
文章版权归作者所有,未经允许请勿转载,侵权请联系 admin@trc20.tw 删除。
THE END