iOS老司机的App启动优化Tips, 让启动速度提升10%
评价一个App是不是一款出色的应用, 第一印象很重要.
这就要求我们必须把App的启动速度的优先级排的很高, 设想一个场景, 点击了App图标, 3秒过去了, 依然还卡在启动状态.这是令用户难以接受的事.
要想解决问题, 首先要对问题有个全面的认识.
下面我们就App启动流程及启动优化实操层面, 做一个抛砖引玉的探讨, 如有错误, 请评论区指正, 先行谢过了:)
1. iOS启动流程分析
App的启动可以分为两种
冷启动又可以概括为3大阶段
dyld
runtime
main.m
热启动(Warm Launch): App之前已经启动好了, 在内存中, 后台运行, 再次点击App图标启动App
App启动时间的优化, 主要是针对冷启动进行.
1.1 打印查看当前App的启动时间
可以通过添加XCode环境变量打印出App的启动耗时
Edit scheme -> Run -> Arguments ,
DYLD_PRINT_STATISTICES
设置为1
.
1.2 App冷启动, 流程分析
启动时间是用户点击App图标, 到第一个界面展示的时间.
以main函数作为分水岭, 启动时间其实包含了两部分,
main函数之前, 分析并加载动态库, 注册需要的类, Category中的方法也会注册到对应的类中, 执行必要的初始化
+load
方法main函数到第一个界面的
viewDidAppear
.所以, 优化也是从两个方面进行的, 建议先优化第二部分, 因为绝大多数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
里启动.项目配置、环境配置、用户信息的初始化、推送、IM等事件, 这些功能在用户进入App首屏之前是必须要加载完的, 放到开屏广告页面的
viewDidAppear
里.其他SDK和配置事件, 由于启动时间不是必须的, 可以放在首屏的
viewDidAppear
方法里, 在这里不会影响启动时间.每次用NSLog方式打印会隐式的创建一个
Calendar
, 因此需要删减启动时各业务的log, 或者仅针对内测版输出log.尽量不要在
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
方法中, 做到延迟加载, 按需加载