xcode编译项目卡死,项目启动后一分钟内xcode还是卡死转圈圈 ------------ APP的冷启动优化,耗时分析

刚接手一个项目,发现打开app后运行xcode会在编译后发生短暂的卡死现象,且app启动之后xcode也会有短暂的卡死现象,变现为:一直转圈圈。

这个问题肯定出在项目配置上,因为是刚启动就卡死,且我的机子是最新款,32内存。大概有了思路就开始定位。

先回顾下app的冷启动流程。

APP冷启动

img

  1. 加载dyld到App进程

    内核会 fork 一个进程,execve 开始加载。检查 Mach-O Header。随后加载 dyld 和程序到 Load Command 地址空间,通过 dyld_stub_binder 开始执行 dyld
  1. 加载动态库(包括所依赖的所有动态库)

    具体步骤如下:
    加载dylib
    
    分析每个dylib(大部分是iOS系统的),找到其Mach-O文件,
    
    打开并读取验证有效性,找到代码签名注册到内核,
    
    最后对dylib的每个segment调用mmap()。

    一般动态库都是苹果自己的;

    注意:如果想针对这一步做优化,因为系统其实对系统库的加载已经做过优化,所以针对这一步骤的优化我们可以做以下尝试:

    • 减少非系统库的依赖。

    • 合并已有的dylib和使用静态库(static archives),减少dylib的使用个数。

  1. Rebase

    dylib加载完成之后,它们处于相互独立的状态,需要绑定起来。

    在dylib的加载过程中,系统为了安全考虑,引入了ASLR(Address Space Layout Randomization)技术和代码签名。
    由于ASLR的存在,镜像(Image,包括可执行文件、dylib和bundle)会在随机的地址上加载,和之前指针指向的地址(preferred_address)会有一个偏差(slide),dyld需要修正这个偏差,来指向正确的地址。
    Rebase在前,Bind在后,Rebase做的是将镜像读入内存,修正镜像内部的指针,性能消耗主要在IO。

    优化该阶段的关键在于减少__DATA segment中的指针数量。我们可以优化的点有:

    • 减少Objc类数量, 减少selector数量。

    • 减少C++虚函数数量

    • 转而使用swift stuct(其实本质上就是为了减少符号的数量)

  1. Bind

    Bind做的是查询符号表,设置指向镜像外部的指针,性能消耗主要在CPU计算。

  1. ObjC setup time

    大部分ObjC初始化工作已经在Rebase/Bind阶段做完了,这一步dyld会注册所有声明过的ObjC类到一张全局表,将分类插入到类的方法列表里。

    这个步骤的优化和前面的优化点差不多,所以我们无需在多做优化。

  1. initializer time

    到了这一阶段,dyld开始运行程序的初始化函数,调用每个Objc类和分类的+load方法,调用C/C++ 中的构造器函数(用attribute((constructor))修饰的函数),和创建非基本类型的C++静态全局变量。Initializers阶段执行完后,dyld开始调用main()函数。
    所以我们针对这一步骤的优化点是:

    • 少在类的+load方法里做事情,尽量把这些事情推迟到+initiailize

    • 减少构造器函数个数,在构造器函数里少做些事情

    • 减少C++静态全局变量的个数

  1. main time

    在这一阶段的优化在于减少didFinishLaunchingWithOptions的工作量,所以我们的重点在于给didFinishLaunchingWithOptions’减负’:

    • 我们知道在使用很多第三方库的时候,很多时候第三方库都推荐在didFinishLaunchingWithOptions完成初始化。但其实我们找到可以延迟加载的库,做延迟加载处理。比如推迟到最先显示的控制器上,或者异步延后加载。
    • 梳理业务逻辑,把可以延迟执行的逻辑,做延迟执行处理。比如检查新版本、注册推送通知等逻辑。
    • 避免在首页控制器的viewDidLoad和viewWillAppear做太多事情。

示例:xcode -> product->scheme->run->arguments->enviroment Variables 添加DYLD_PRINT_STATISTICS : 1

image-20200929171545354

Total pre-main time: 401.36 milliseconds (100.0%)

dylib loading time: 186.35 milliseconds (46.4%)

rebase/binding time: 10.47 milliseconds (2.6%)

ObjC setup time: 24.21 milliseconds (6.0%)

initializer time: 180.17 milliseconds (44.8%)

slowest intializers :(这个列出的是最慢的几个dylib文件。)

libSystem.B.dylib : 5.69 milliseconds (1.4%)

libMainThreadChecker.dylib : 25.29 milliseconds (6.3%)

那么回到我们遇到的问题,应该是initializer time。

经过分析发现,删除某个模块将会相应减少卡死时间,但又不是特定删除某个模块就解决了,也就是说,没有一个模块能逃脱的了干系,那就是全局的影响。项目配置看了下没啥问题,那就剩下prefix文件没看了。果然,prefix文件里有很多的头文件,还有造成头文件循环引用的。。。但是这个也不至于卡死,难道太多会造成这样?

慢慢解耦掉,发现*initializer time在不断减少,但是卡死现象还是有,继续。

知道删除了masonry导入后,发现卡死现象直接没了。我又把其他头文件恢复,测试只删除masonry,也是直接解决了卡死现象。那这就奇怪了,为啥masonry影响这么大呢?

但是其他项目试着在pch导入masonry,没有这么卡死的现象,仅这个项目有,虽然解决了,但是原因暂时还是没发现。

看下masonry源码。

     问题1:这是应用一对一听课或一对多听课吗?

答:此应用中的上课模块,是一对多的,文字方式的授课。

问题2:应用程序提供直播在线听课或录制的视频课件吗?
答:不提供直播在线听课或者视频课件。文字形式(点击可播放文字对应的音频)的听课。效果如下附件图2-1.jpg。

问题3:目标受众是谁?
答:公司内部员工。可在“我的”页面点击“加入企业”输入邀请码BlmXnpJl以加入企业,如图3-1.jpg(测试账号已经加入过企业)。

问题4:你的应用程序访问任何付费内容或服务吗?
答:此应用不访问任何付费内容或服务,全部免费。

问题5:描述应用程序中任何付费内容或服务,以及支付方式。
答:此应用不不涉及任何付费内容或服务,无支付。

问题6:是个人客户为内容或服务付费,还是公司为其用户购买内容或服务?
答:此应用不不涉及任何付费内容或服务,无支付,输入邀请码即可加入本公司学习内容。加入方式见图3-1.jpg。

问题7:这个应用程序是用于一个公司还是多个客户公司?
答:一个公司。

问题8:该应用程序是为某一特定公司的内部使用(员工、合作伙伴等)吗?
答:是的。

问题9:用户如何取得帐户?
答:用户自行创建账号,随后在“我的”页面点击“加入企业”输入邀请码BlmXnpJl以加入企业,需要后台审核确定为本公司员工后通过,如图3-1.jpg(测试账号已经加入过企业)。

问题10:创建帐户是否涉及费用?
答:不涉及费用,全部免费。

问题11:这个应用程序将主要在哪些国家发行?
答:主要,且仅在中国发行。

mach-o

fishhook

dyld


   转载规则


《xcode编译项目卡死,项目启动后一分钟内xcode还是卡死转圈圈 ------------ APP的冷启动优化,耗时分析》 采用 知识共享署名 4.0 国际许可协议 进行许可。
 上一篇
导出iOS的应用ipa包文件 导出iOS的应用ipa包文件
1. Apple Configurator 2在App Store搜索“Apple Configurator 2”下载安装(最低适配macOS 10.14)准备工作: Apple ID账号密码, Apple设备。步骤: 1.1 连接设备到
2020-10-09
下一篇 
iOS 查看项目编译时间并优化编译速度 iOS 查看项目编译时间并优化编译速度
源代码和三方库引入很多的时候,我们会发现编译的速度很慢。在了解了XCode的编译过程后,我们可以从以下角度来优化编译速度:A.查看编译时间方法: 1.关闭XCode 2.终端输入指令: defaults write com.apple.dt
2020-09-28
  目录