美文网首页
07--应用加载02--应用加载流程[_objc_init][r

07--应用加载02--应用加载流程[_objc_init][r

作者: 修_远 | 来源:发表于2020-07-21 22:35 被阅读0次
    TOC

    _objc_init:初始化流程

    • _objc_init 源码
    void _objc_init(void)
    {
        static bool initialized = false;
        if (initialized) return;
        initialized = true;
        
        // fixme defer initialization until an objc-using image is found?
        environ_init();
        tls_init();
        static_init();
        runtime_init();
        exception_init();
        cache_init();
        _imp_implementationWithBlock_init();
    
        _dyld_objc_notify_register(&map_images, load_images, unmap_image);
    }
    

    我们先不要着急分析流程,看到最后一行代码:_dyld_objc_notify_register。这个很明显是 _dyld 里面的一个方法,上一篇文章中分类了 _dyld 的加载流程。

    App加载分析

    所以这里从 _dyld 中将这个方法的源码拿了过来,下面是 _dyld_objc_notify_register 的源码

    void _dyld_objc_notify_register(_dyld_objc_notify_mapped    mapped,
                                    _dyld_objc_notify_init      init,
                                    _dyld_objc_notify_unmapped  unmapped)
    {
        dyld::registerObjCNotifiers(mapped, init, unmapped);
    }
    
    void registerObjCNotifiers(_dyld_objc_notify_mapped mapped, _dyld_objc_notify_init init, _dyld_objc_notify_unmapped unmapped)
    {
        // record functions to call
        sNotifyObjCMapped   = mapped;
        sNotifyObjCInit     = init;
        sNotifyObjCUnmapped = unmapped;
        ……
    }
    

    源码很简单,仅仅是记录了这三个方法的地址,在需要的时候进行一个调用。在我们平时开发中,需要用到地址传递的时候,一般是需要修改这个变量的值,那么可以猜测这个方法的作用是将 _dyld 中的 mappedImage 回传给 objc,这里的对内容放在后面分析,下面对这些初始化方法进行一个简单的介绍。

    environ_init();

    读取影响运行时的环境变量。如果需要,可以打印环境变量帮助。

    1. 在终端中输入 export OBJC_HELP=1 可以输出所有环境变量

    OBJC_HELP

    如果没有输出,尝试再执行一个 open 命令

    2. 在Xcode 源码中输出环境变量

    在这里手动修改:PrintHelp=true;

    image

    3. OBJC_DISABLE_NONPOINTER_ISA 环境变量

    • 设置 OBJC_DISABLE_NONPOINTER_ISAYES

      image

      设置之后的打印出来的isa结果

      image

      设置之前的isa结果

      image

      因为系统做了优化,对isa联合体做了平移操作,所以会不一样
      OBJC_PRINT_LOAD_METHODS

    4. 环境变量 OBJC_PRINT_LOAD_METHODS

    • 设置 OBJC_PRINT_LOAD_METHODS 为 YES

      image
    • 打印所有的 +load 方法

      image

    作用:可以对这些方法做方法优化,避免全局搜索找到的load方法(可能没有用到)

    tls_init();

    关于线程的绑定,比如每个线程数的析构函数

    static_init();

    • 运行C++ 的静态构造函数。
    • 在 _dyld 调用我们自己的静态构造函数之前调用
    • libc 会调用 _objc_init(),所以我们必须自己做。

    runtime_init();

    是空实现!就是说 objc 的锁是完成采用C++那一套的, oc中不需要做任何处理。

    exception_init();

    初始化 libobjc 的异常处理系统。比如监控下一行注册异常的回调的代码。

    cache_init();

    _imp_implementationWithBlock_init()

    _dyld_objc_notify_register()

    仅供 objc 运行时使用
    注册处理程序,以便在映射、取消映射和初始化 objc-image 时调用
    Dyld将使用包含 objc-image-info 的景象文件的数组,回调 mapped 函数

    • 研究对象
      • map_images(重点)
      • load_images(重点)
      • unmap_image:主要做卸载相关的操作,不做重点研究

    _objc_init:map_images 流程

    read_images 注释

    /***********************************************************************
    * map_images
    * Process the given images which are being mapped in by dyld.
    * Calls ABI-agnostic code after taking ABI-specific locks.
    *
    * Locking: write-locks runtimeLock
    **********************************************************************/
    void
    map_images(unsigned count, const char * const paths[],
               const struct mach_header * const mhdrs[])
    {
        mutex_locker_t lock(runtimeLock);
        return map_images_nolock(count, paths, mhdrs);
    }
    

    先进入 map_images,这个函数的注释说“处理给定的镜像文件(在dyld中映射进去的镜像)”,这句注释正好验证了我们上面的猜测——registerObjCNotifiers 方法的作用是将 _dyld 中的 mappedImage 回传给 objc

    下面来分析 map_images_nolock 方法的流程

    read_images 要读懂的问题

    1. 我们的dyld主题的思维是加载库-镜像文件,但是镜像文件怎么读取的?
    2. 我们的macho里面的数据怎么到我们的内存?
    3. 有没有不在macho里面的数据同样也可以在内存找到?
    4. sel方法编号的加载?
    5. 什么是懒加载类和非懒加载类?
    6. 类是如何加载实现的 -ro-rw 的关系?
    7. 协议&分类里面的数据是如何加载的?

    read_images 初体验

    在dyld的源码中去查找_dyld_objc_notify_register方法实现

    image

    1. 如何开展dyld研究

    • 看注释 “// record functions to call”——记录被调用的函数
    • 可以看出来,如果要研究这三个参数,和明显要研究这三个函数被调用的地方
    • 以第一个参数为例,找到第一个函数被调用的地方
    image

    2. 在objc源码中分析,定位到_read_images方法

    • _objc_init->map_images

      image
    • map_images->map_images_nolock

      image
    • map_images_nolock->_read_images

      image

    3. 怎么分析 _read_images方法

    这个方法有400多行,逐行读肯定不行

    1. 我们先将所有有注释的代码块折叠起来

    2. 方法的准备条件

      image
    3. 方法的流程

      image

      我们可以看到每个代码块都有很标准的注释和日志输出,整个流程一目了然:

      1. 加载所有类到类的 gdb_objc_realized_classes 表中
      2. 对所有类做重映射
      3. 将所有SEL都注册到 namedSelecotors 表中
      4. 修复函数指针遗留
      5. 将所有 Protocol 都添加到 protocol_map 表中
      6. 对所有 Protocol 做重映射
      7. 初始化所有非懒加载的类,进行 rw、ro等操作
      8. 遍历已标记的懒加载的类,并做初始化操作
      9. 处理所有 Category,包括 ClassMeta Class
      10. 初始化所有未初始的类。

    4. 表的介绍

    • gdb_objc_realized_classes:无论是否实现,只要不在 dyld 共享缓存中的已命名类的表
    • allocatedClasses:通过 objc_allocateClassPair 已分配的所有类(和元类)的表
    • namedSelecotors:方法编号的表
    • protocol_map:协议的表

    题外话

    1. 为什么类的开头是 NS
      NextStep的简称
      乔帮主曾经被苹果踢出去了,然后又回来了
    2. NX开头的又代表什么含义
      NX表示一种CPU的计数,“禁止执行的意思”

    read_images 流程分析

    第一流程:查找类。修复未解决的future类。标记 bundle类。

    流程代码
    1. 从编译后的类列表中取出所有类, 获取到的是一个 classref_t 类型指针
      classref_t *classlist = _getObjc2ClassList(hi, &count);

    2. 遍历数组中会去除 OS_dispatch_queue_concurrent OS_xpc_object NSRunloop 等系统类, 例如 CFFoundation libdispatch 中的类, 以及自己创建的类.

      通过readClass函数获取处理后的新类, 内部主要操作 ro 和 rw 结构体

    3. 初始化所有懒加载的类需要的内存空间,现在数据没有加载

      image

    第二流程:修复类的重映射(一般不会走进来)

    image

    注释

    1. 类列表和非懒加载类类表保持未重映射
    2. 重映射类和super类,用于消息分发
    3. 将未映射class和super class重映射,被remap的类都是非懒加载类

    第三流程:修复SEL引用

    image

    将所有 SEL 都注册到 namedSelecotors 表中

    第四流程:修复旧的objc_msgSend_fixup调用站点,有条件才进来,不做分析

    image

    第五流程:查找协议protocols。修复协议protocols引用

    image

    _getObjc2ProtocolList 读协议存到 protocol_map

    第六流程:修复协议的引用

    image

    预先优化的图像可能已经在正确的位置了,但并不能确定。所以需要重新映射协议的引用

    第七流程:实现非懒加载类(重点流程)

    • 实现了load方法的类
    • 静态实例变量的类(常见的是单例)
    image

    第八流程:实现未来类。(条件依赖第一个流程)

    image

    第九流程:查找分类。(默认找的是懒加载分类-实现了load)

    • 注册分类并关联目标类
    • 重新构建类的方法列表(attachLists流程)

    因为分类的方法也是放到了类的方法列表里面,而且是通过 attachLists 流程,插入了方法列表的前面,所以会造成分类方法“覆盖”主类方法的现象。

    image

    第十流程:实现所有未实现的类

    image

    相关文章

      网友评论

          本文标题:07--应用加载02--应用加载流程[_objc_init][r

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