iOS【启动优化】

冷启动 VS 热启动

04.png
05.png

如果你刚刚启动过App,这时候App的启动所需要的数据仍然在缓存中,再次启动的时候称为热启动。如果设备刚刚重启,然后启动App,这时候称为冷启动。

启动时间在小于400ms是最佳的,因为从点击图标到显示Launch Screen,到Launch Screen消失这段时间是400ms。启动时间不可以大于20s,否则会被系统杀掉。

以main函数作为分水岭,启动时间其实包括了两部分:main函数之前和main函数到第一个界面的viewDidAppear:。所以,优化也是从两个方面进行的,个人建议优先优化后者,因为绝大多数App的瓶颈在自己的代码里。

Main函数之后

我们首先来分析下,从main函数开始执行,到你的第一个界面显示,这期间一般会做哪些事情。

*   执行AppDelegate的代理方法,主要是didFinishLaunchingWithOptions
*   初始化Window,初始化基础的ViewController结构(一般是UINavigationController+UITabViewController)
*   获取数据(Local DB/Network),展示给用户。

AppDelegate

通常我们会在AppDelegate的代理方法里进行初始化工作,主要包括了两个方法:

didFinishLaunchingWithOptions
applicationDidBecomeActive
优化这些初始化的核心思想就是:

能延迟初始化的尽量延迟初始化,不能延迟初始化的尽量放到后台初始化。

这些工作主要可以分为几类:

三方SDK初始化,比如Crash统计; 像分享之类的,可以等到第一次调用再出初始化。
初始化某些基础服务,比如WatchDog,远程参数。
启动相关日志,日志往往涉及到DB操作,一定要放到后台去做
业务方初始化,这个交由每个业务自己去控制初始化时间。

Main函数之前

Main函数之前是iOS系统的工作,所以这部分的优化往往更具有通用性。

dylibs

在每个动态库的加载过程中, dyld需要:

1.  分析所依赖的动态库
2.  找到动态库的mach-o文件
3.  打开文件
4.  验证文件
5.  在系统核心注册文件签名
6.  对动态库的每一个segment调用mmap()
通常的,一个App需要加载很多个dylibs, 但是其中的系统库被优化,可以很快的加载。应用所依赖的dylib文件可能会再依赖其他 dylib,所以dyld所需要加载的是动态库列表一个递归依赖的集合。
针对这一步骤的优化有:

1.  减少非系统库的依赖
2.  合并非系统库

启动的第一步是加载动态库,加载系统的动态库使很快的,因为可以缓存,而加载内嵌的动态库速度较慢。所以,提高这一步的效率的关键是:减少动态库的数量。

  • 合并动态库,比如公司内部由私有Pod建立了如下动态库:XXTableView, XXHUD, XXLabel,强烈建议合并成一个XXUIKit来提高加载速度。

Rebase & Bind & Objective C Runtime

由于ASLR(address space layout randomization)的存在,可执行文件和动态链接库在虚拟内存中的加载地址每次启动都不固定,所以需要这2步来修复镜像中的资源指针,来指向正确的地址。 rebase修复的是指向当前镜像内部的资源指针; 而bind指向的是镜像外部的资源指针。
rebase步骤先进行,需要把镜像读入内存,并以page为单位进行加密验证,保证不会被篡改,所以这一步的瓶颈在IO。bind在其后进行,由于要查询符号表,来指向跨镜像的资源,加上在rebase阶段,镜像已被读入和加密验证,所以这一步的瓶颈在于CPU计算。
优化该阶段的关键在于减少__DATA segment中的指针数量。我们可以优化的点有:

1.  减少Objc类数量, 减少selector数量
2.  减少C++虚函数数量

Rebase和Bind都是为了解决指针引用的问题。对于Objective C开发来说,主要的时间消耗在Class/Method的符号加载上,所以常见的优化方案是:

*   减少`__DATA`段中的指针数量。
*   合并Category和功能类似的类。比如:UIView+Frame,UIView+AutoLayout…合并为一个
*   删除无用的方法和类。
*   多用Swift Structs,因为Swfit Structs是静态分发的。

Initializers

通常,我们会在+load方法中进行method-swizzling,这也是Nshipster推荐的方式。

  • 用initialize替代load。不少同学喜欢用method-swizzling来实现AOP去做日志统计等内容,强烈建议改为在initialize进行初始化。
  • 减少__atribute__((constructor))的使用,而是在第一次访问的时候才用dispatch_once等方式初始化。
  • 不要创建线程
  • 使用Swfit重写代码。

pre-main阶段耗时的影响因素:

动态库加载越多,启动越慢。
ObjC类越多,函数越多,启动越慢。
可执行文件越大启动越慢。
C的constructor函数越多,启动越慢。
C++静态对象越多,启动越慢。
ObjC的+load越多,启动越慢。

整体上pre-main阶段的优化有:

①减少依赖不必要的库,不管是动态库还是静态库;如果可以的话,把动态库改造成静态库;
如果必须依赖动态库,则把多个非系统的动态库合并成一个动态库;

②检查下 framework应当设为optional和required,
如果该framework在当前App支持的所有iOS系统版本都存在,那么就设为required,否则就设为optional,
因为optional会有些额外的检查;

③合并或者删减一些OC类和函数;
关于清理项目中没用到的类,使用工具AppCode代码检查功能,查到当前项目中没有用到的类(也可以用根据linkmap文件来分析,但是准确度不算很高);

④删减一些无用的静态变量,

⑤删减没有被调用到或者已经废弃的方法。

⑥将不必须在+load方法中做的事情延迟到+initialize中,尽量不要用C++虚函数(创建虚函数表有开销)
因为load是在启动的时候调用,而initialize是在类首次被使用的时候调用,不过当你把load中的逻辑移到initialize中时候,一定要注意initialize的重复调用问题。

⑦类和方法名不要太长:iOS每个类和方法名都在__cstring段里都存了相应的字符串值,所以类和方法名的长短也是对可执行文件大小是有影响的;
因还是object-c的动态特性,因为需要通过类/方法名反射找到这个类/方法进行调用,object-c对象模型会把类/方法名字符串都保存下来;

⑧用dispatch_once()代替所有的 attribute((constructor)) 函数、C++静态对象初始化、ObjC的+load函数;

⑨在设计师可接受的范围内压缩图片的大小,会有意外收获。
压缩图片为什么能加快启动速度呢?因为启动的时候大大小小的图片加载个十来二十个是很正常的,
图片小了,IO操作量就小了,启动当然就会快了,比较靠谱的压缩算法是TinyPNG。

2、抽象重复代码

1、在iOS代码中可能会为同一个类写很多分类方法,由于参与开发同学较多,可能会导致方法重复,但是实际上运行起来只能有一个分类的方法被调用,这取决于哪个分类后被加载,然而编译的二进制代码中,两个方法应该是都存在的,这不仅会增加app体积,也会增加启动时间,所以应该杜绝这样的重复问题;
2、有很多地方可能是名字不同,但是函数的功能相同,这个不容易被发现,需要大家在写代码的过程中注意;
3、又或者两个函数名字比较接近,里面有很多相似的代码,这种情况下可以进行相同的代码的提取。

main阶段的优化大致有如下几个点:

①减少启动初始化的流程,能懒加载的就懒加载,能放后台初始化的就放后台,
能够延时初始化的就延时,不要卡主线程的启动时间,已经下线的业务直接删掉;
②优化代码逻辑,去除一些非必要的逻辑和代码,减少每个流程所消耗的时间;
③启动阶段使用多线程来进行初始化,把CPU的性能尽量发挥出来;
④使用纯代码而不是xib或者storyboard来进行UI框架的搭建,尤其是主UI框架比如TabBarController这种,
尽量避免使用xib和storyboard,因为xib和storyboard也还是要解析成代码来渲染页面,多了一些步骤;

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,907评论 6 506
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,987评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,298评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,586评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,633评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,488评论 1 302
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,275评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,176评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,619评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,819评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,932评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,655评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,265评论 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,871评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,994评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,095评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,884评论 2 354