美文网首页
启动优化的原理以及操作

启动优化的原理以及操作

作者: Harry__Li | 来源:发表于2022-01-13 22:09 被阅读0次

    首先我们要知道,启动是分为冷启动和热启动的。启动优化主要就是优化冷启动的时间。
    冷启动又可以分为两个部分:pre_main阶段和main函数之后。

    pre_main阶段:main函数之前,系统加载可执行文件到内存,执行一系列的加载链接等操作。dyld的加载过程。
    main函数之后:从main函数开始,到构建第一个见面,渲染完成。

    main函数之前

    在优化main函数之前的加载过程之前,我们是不是首先要看看他的耗时。具体怎么怎么加载,然后我们再从这些方面入手。

    • 检测耗时
      在Environment Variables --> 点击+添加环境变量 DYLD_PRINT_STATISTICS设为YES,运行项目,就可以打印出对应的耗时时间。如下图所示


      截屏2022-01-11 下午2.27.41.png
      截屏2022-01-11 下午2.30.05.png

      第二图就是最终打印出的耗时时间。看了上面的打印输出,有点懵,这都是些什么?不要着急 接下来慢慢看他的流程局明白了。

    • 加载加载过程


      截屏2022-01-12 下午4.27.37.png

      上图是我自己总结画出的一个流程图。看不懂没关系,我们来一个一个的了解。

    镜像

    我们首先要明白,每个app都是以images为单位进行加载的。而镜像的类型包括:

    • 1.executable:应用的二进制可执行文件;
    • 2.dylib:动态链接库;
    • 3.bundle:资源文件,属于不能被链接的 dylib
      明白了镜像以后我们在看上面的图。系统通过 fork() 方法创建了一个进程,然后执行镜像通过exec() 来替换为另外一个可执行程序。
    Mach-O

    刚开始不知道这是个啥玩意,后来查了一些资料博客才明白。
    Mach-O:是一种用于记录可执行文件、对象代码、动态加载代码和内存转存的文件格式。app生成的二进制可执行文件就是Mach-O格式的。ios所有的类编译后都会生成对应的目标文件.o文件,而这个可执行文件就是这些.o文件集合。

    Mach-O 文件主要由三部分组成:

    • 1.Mach header:描述 Mach-O 的 CPU 架构、文件类型以及加载命令等;
    • 2.Load commands:描述了文件中数据的具体组织结构,不同的数据类型使用不同的加载命令;
    • 3.Data:Data 中的每个段(segment)的数据都保存在这里,每个段都有一个或多个 Section,它们存放了具体的数据与代码,主要包含这三种类型:
      1.__TEXT 包含 Mach header,被执行的代码和只读常量(如C 字符串)。只读可执行(r-x)。
      2.__DATA 包含全局变量,静态变量等。可读写(rw-)。
      3.__LINKEDIT 包含了加载程序的元数据,比如函数的名称和地址。只读(r–-)
    dylib

    后缀名为.dylib的文件就是动态库。动态库是运行时加载的,可以背多个app公用(系统的动态库)。
    这里动态库又分为了两种:系统的dylib和内嵌dylib(也就是我们自己引入的);

    • ios中用到的所有系统 framework,比如 UIKit、Foundation;
    • 系统级别的 libSystem(如 libdispatch(GCD) 和 libsystem_blocks(Block))
    • 注意:系统的动态库都是可以共享的,他可以让手机上的其他app公用。但是开发者自己引入的动态库是不能共享的,在一个项目中你引入了一个自己创建的动态库,在手机上的其他app能用嘛?肯定是不可能使用的。
      后续会了解动态库和静态库的优缺点和区别。
    dyld

    dyld:Dynamic Link Editor,看英文就可以理解出来,它是动态链接器。它是专门用来加载dylib文件的。

    上面是一些我们需要知道的知识点。下面来详细说明启动的过程。

    参考流程图我们知道,启动应用时,系统会通过fork()创建一个进程。然后执行镜像通过exec() 来替换为另一个可执行程序。然后执行如下的操作
    1.把可执行文件加载到内存空间,从可执行文件中能够分析出dyld的路径。
    2.把dyld加载到内存。
    3.Load Dylibs:(1)、分析app依赖的所有dylib动态库(2)找到dylib对应的mach-o文件(3)打开、读取这些mach-o文件,并验证其有效性。(4)在系统内核中注册代码签名
    4.Rebase/Binding指针重定位:
    在dylib加载的过程中,系统为了安全考虑,引入了ASLR技术和代码签名。由于ASLR的存在,镜像会在新的随机地址上加载,和之前指针指向的地址会有一个偏差。所以,指针数量越少,指针修复的耗时也越少。
    5.ObjC Setup
    (1)dyld会注册所有声明过的objc类。
    (2)将分类插入到类的方法列表中
    (3)检查每个selector的唯一性。
    6.Initializers和load:开始动态调整,往堆和栈中写入内容
    (1)调用每个 Objc 类和分类中的 +load 方法
    (2)调用 C/C++ 中的构造器函数(用 attribute((constructor)) 修饰的函数)
    7.main() 的初始化。

    pre_main优化操作

    既然明白了main函数之前的操作过程,那么我们针对他的每一个过程就可以来优化

    dyld
    • 尽量不适用内嵌的dylib,也就是说别自己瞎胡创建动态库。系统的动态库都做了优化的。我们自己创建的加载很耗时。
    • 如果必须使用自己的动态库,可以合并我们创建的dylib
    • 可以使用静态库代替。(静态库会在编译是被打进可执行文件,造成文件体积变大)
    Rebase/Binding

    这个过程是dyld调整修复地址,所以指针越少,耗时就越少。

    • 我们可以减少objc类、方法、分类的数量。删除无效的类和方法。(可以使用第三方fui检测无效的类,使用pytho检测无效的方法)
    • 减少c++虚函数
    Initializers
    • 尽量避免在类的 +load 方法中初始化,可以推迟到 +initiailize 中进行;(因为在一个 +load 方法中进行运行时方法替换操作会带来 4ms 的消耗)

    main函数阶段

    查看main函数阶段的耗时

    1.https://github.com/beiliao-mobile/BLStopwatch 创建了一个单例类,然后把每次的时间都加入到数组中。具体的操作可以查看git
    2.使用xcode自带的。
    Xcode → Open Developer Tool → Instruments → Time Profiler。
    1.配置 Scheme。点击 Edit Scheme 找到 Profile 下的 Build Configuration,设置为 Debug。

    1. 配置 PROJECT。点击 PROJECT,在 Build Settings 中直接搜 Debug Information Format,把 Debug 对应的值改为 DWARF with dSYM File。
      3.启动Time Profiler。上选择Call Tree 中的 Separate Thread 和 Hide System Libraries。然后就可以查看启动时间。
    启动优化

    关于app的初始化,除了统计、日志这种必须要在app一启动就配置的事件,有一些配置也可以考虑延迟。可以从下面的一些角度做优化:

    • 用纯代码的方式,而不是用xib/sb来加载首页视图
    • 可以创建一个工具单例类,根据不能的业务场景初始化。延迟暂时不需要的第三方加载,延迟执行部分的业务逻辑和UI配置。在工具类中,我们可以把必须在启动初始化的放到一个方法中。然后把首页初始化的在一个方法中,后期更方便维护。
    • 在release包中移除NSLOG打印
    • 在视觉可接受的范围内,压缩页面中的图片大小。
    • 耗时操作,放在子线程中完成

    相关文章

      网友评论

          本文标题:启动优化的原理以及操作

          本文链接:https://www.haomeiwen.com/subject/budvcrtx.html