美文网首页
OC底层探索(十五): 类的加载(三)

OC底层探索(十五): 类的加载(三)

作者: ShawnAlex | 来源:发表于2022-05-19 17:25 被阅读0次

    所用版本:

    • 处理器: Intel Core i9
    • MacOS 12.3.1
    • Xcode 13.3.1
    • objc4-838

    建议先看下
    OC底层探索(十三): 类的加载(一)
    OC底层探索(十四): 类的加载(二)

    接上次继续看realizeClassWithoutSwift实现类方法

    realizeClassWithoutSwift

    remapClass

    自定义类跟断点走, 可发现先走remapClass

    remapClass

    对类指针做修正

    [ 第一步:生成数据 ]

    生成ro数据
    • auto ro = (const class_ro_t *)cls->data();: 取转换cls中的data数据,再强制转为class_ro_t *, 赋值给ro
    data()方法
    data()方法

    看下内部可看出, data() 内部: bits & FAST_DATA_MASK 与操作拿到data(), 而这里的data()是个地址指针 class_rw_t *类型, 获取之后外层再强制转成class_ro_t *指针。
    留意下, 几乎所有引用到class_ro_t的地方都会使用了const关键字做修饰, 苹果希望ro像是一个静态, 不愿意被外界随意修改的属性, 而rw无.

    关于那个强转稍微说下, class_rw_tclass_ro_t2个结构体虽然有点差距, 但是ro需要的部分rw都可以找到, 可以说是一一给赋值关系。

    举个例子: 设置一个Method方法

    Method

    其中class_getInstanceMethod返回是一个Method *类型

    Method class_getInstanceMethod(Class cls, SEL sel)
    

    OK没问题, 那么我们只用 Methodmethod_name, method_imp写成一个新的结构体

    objc_method

    发现也是正常运行, 通过打印信息可发现, 只有2个成员。这个是就跟class_rw_t *强转class_ro_t *一样, 只取对应信息强转调用。但是也要留意两者是格式对应或包含关系, 如果格式地址无法解析或者不匹配到位, 会发生解析不出来情况。

    生成rw数据

    继续往下走, 可发现

    • rw初始化
    • ro数据存入rw
    • 设置 flags标识符uint32_t, 1<<31 | 1<<19 | 1<<0, 31位表示rw是否已经初始化完毕RW_REALIZED,19位表示rw是否初始化中RW_REALIZING,元类为1,非元类为0。
    • 最后将rw存入clsdata中( 话说苹果设计这么多叫data的, 不嫌绕么:) )

    [ 第二步:完善父类链元类链 ]

    完善父类链元类链
    • 递归方法realizeClassWithoutSwift 完善父类链, 元类链。
    完善类
    • 如果是元类, 更改isa类型为raw isa( 纯isa), 回顾下isa联合体位域
    #   define ISA_MASK        0x0000000ffffffff8ULL
    #   define ISA_MAGIC_MASK  0x000003f000000001ULL
    #   define ISA_MAGIC_VALUE 0x000001a000000001ULL
    #   define ISA_BITFIELD                                                      \
          uintptr_t nonpointer        : 1;                                       \
          uintptr_t has_assoc         : 1;                                       \
          uintptr_t has_cxx_dtor      : 1;                                       \
          uintptr_t shiftcls          : 33; /*MACH_VM_MAX_ADDRESS 0x1000000000*/ \
          uintptr_t magic             : 6;                                       \
          uintptr_t weakly_referenced : 1;                                       \
          uintptr_t deallocating      : 1;                                       \
          uintptr_t has_sidetable_rc  : 1;                                       \
          uintptr_t extra_rc          : 19
    #   define RC_ONE   (1ULL<<45)
    #   define RC_HALF  (1ULL<<18)
    
    

    其中 nonpointer:表示是否对isa指针开启优化。0:纯isa指针,1:不只是类对象地址,isa中包含了类信息、对象的引用计数等。其中大部分的类都是nonpointer类,元类不是nonpointer

    接下来是对几种特殊情况的纯指针处理

    特殊情况的纯指针
    isa关系
    • 环境变量为DisableNonpointerIsa, 禁止NonpointerIsa
    • 非hackedDispatch, 且当前为OS_object类,
    • 继承链相关需要为纯指针等

    也都要, 要将isa改变成raw isa

    关联父类, 元类
    • 父类链, 元类链处理完之后进行关联
    调整父类ro

    如果是父类需要更多的内存空间, 会做个ro做调整

    reconcileInstanceVariables reconcileInstanceVariables gdb_objc_class_changed

    找最大的实例变量做个重新对齐, 并与父类ro作比较, 如果没超出范围直接返回, 超出就重新设置ro

    举个例子: 父类SRTest, 子类SRTestSub, 父类里面2个NSString *, 子类什么也没有

    例子
    运行一下
    例子
    • 父类 isa指针 + 2 * 指针 = 8 + 2 * 8 = 24, 系统16字节对齐结果 = 32字节
    • 子类isa = 8字节, 但是实际依然要开辟 = 32字节

    有点父欠子还那味儿了, 回到realizeClassWithoutSwift 继续看

    setInstanceSize setInstanceSize

    ro计算内存大小存到缓存中, 下次就从缓存中取

    处理标识

    处理一些Flag标志位

    • 拷贝ro的一些flagsrw
    • RO_HAS_CXX_STRUCTORS 标记c++构造方法
    • RO_HAS_CXX_DTOR_ONLY 标记c++析构方法
    • 处理一些禁止标志位: 自己禁止关联对象或者父类禁止关联对象 (父类如果禁止,子类同步禁止), 则同步RW_FORBIDS_ASSOCIATED_OBJECTS标记。
    子类关联

    父类子类进行关联

    addSubclass
    • 改变父子类data()中对应类字段, 进行关联

      • 子类: nextSiblingClass
      • 父类: firstSubclass
    • 父类如果有CXX相关, 子类同步

    • 父类如果设置不允许预缓存, 子类也不允许

    • 父类如果设置不允许内联sel, , 子类也不允许

    • 父类如果的isa纯指针, 子类同步。

    ...不得不说, 父亲管的是真严格啊 :), 根类是单独设置的, 也稍微看一下

    addRootClass
    /***********************************************************************
    * _firstRealizedClass
    * The root of all realized classes
    **********************************************************************/
    static Class _firstRealizedClass = nil;
    

    相邻为设置为nil, 自己设置为NSObject

    [ 第三步:methodizeClass ]

    继续往下走可看到进行走方法methodizeClass

    methodizeClass

    methodizeClass 主要从ro中读取方法列表(包括分类中的方法), 属性列表, 协议列表等 赋值给rw,并返回cls。 看下内部

    methodizeClass
    • 先获取ro, rw, rwe
    方法/属性/协议获取
    • rwe 存在情况下
      • rwe 添加方法列表
      • rwe 添加属性列表
      • rwe 添加协议列表
    根元类
    • 根元类需要添加 initialize 方法。

    再往下看都是分类加载内容, 放在下一篇: OC底层探索(十六): 类的加载(四)

    相关文章

      网友评论

          本文标题:OC底层探索(十五): 类的加载(三)

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