[OC 底层] (二)类与对象底层原理

OC对象本质

文章目录

探索对象本质

  • 在 main 中自定义一个类 Person, 有一个属性 name
objc 复制代码
@interface Person : NSObject
@property (nonatomic, copy) NSString *name;
@end

@implementation Person
@end
  • 我们可以通过终端利用 clang 将 main.m编译成 main.cpp,有以下几种编译命令,这里使用了第一种
objc 复制代码
//1、将 main.m 编译成 main.cpp
clang -rewrite-objc main.m -o main.cpp

//2、将 ViewController.m 编译成  ViewController.cpp
clang -rewrite-objc -fobjc-arc -fobjc-runtime=ios-13.0.0 -isysroot / /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator13.7.sdk ViewController.m

//以下两种方式是通过指定架构模式的命令行,使用xcode工具 xcrun
//3、模拟器文件编译
- xcrun -sdk iphonesimulator clang -arch arm64 -rewrite-objc main.m -o main-arm64.cpp 

//4、真机文件编译
- xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m -o main- arm64.cpp 
  • 编译之后,我们可以看到这里的类的一个定义
objc 复制代码
//NSObject的定义
@interface NSObject <NSObject> {
    Class isa  OBJC_ISA_AVAILABILITY;
}

//NSObject 的底层编译
struct NSObject_IMPL {
    Class isa;
};
struct Person_IMPL {
	struct NSObject_IMPL NSObject_IVARS;
	//第一个成员并不是一个新的 `isa`,而是直接把整个 `NSObject_IMPL` 结构体原样嵌入进来并命名为 `NSObject_IVARS`由于 NSObject_IMPL 内部只有一个 Class isa 成员,所以 Person_IMPL的第一个字段在内存上就是一个 Class isa
	NSString *_name;
};

在这里我们可以发现 isa 的类型是 Class

  • 在上一篇博客中[[[OC 底层] (一) alloc, init & new]],提到过initInstanceIsa方法,这里面初始化 isa 指针的时候,我们是通过 isa_t类型初始化的。
  • 在 NSObject 中定义的 isa 类型是 Class,这是需要做一个强制转换的,目的是让开发人员清晰明确。

总结

  • OC 本质上是结构体
  • Person 中的 isa 是继承自 NSObject 中的 isa

objc_setProperty 源码源码

这里面有一个 set 方法:

objc 复制代码
static void _I_Person_setName_(Person * self, SEL _cmd, NSString *name) { objc_setProperty (self, _cmd, __OFFSETOFIVAR__(struct Person, _name), (id)name, 0, 1); }

我们可以看到里面调用了 objc_setProperty

objc 复制代码
void objc_setProperty(id self, SEL _cmd, ptrdiff_t offset, id newValue, BOOL atomic, signed char shouldCopy) 
{
    bool copy = (shouldCopy && shouldCopy != MUTABLE_COPY);
    bool mutableCopy = (shouldCopy == MUTABLE_COPY);
    reallySetProperty(self, _cmd, newValue, offset, atomic, copy, mutableCopy);
}

static inline void reallySetProperty(id self, SEL _cmd, id newValue, ptrdiff_t offset, bool atomic, bool copy, bool mutableCopy)
{
    if (offset == 0) {
        object_setClass(self, newValue);
        return;
    }

    id oldValue;
    id *slot = (id*) ((char*)self + offset);

    if (copy) {
        newValue = [newValue copyWithZone:nil];
    } else if (mutableCopy) {
        newValue = [newValue mutableCopyWithZone:nil];
    } else {
        if (*slot == newValue) return;
        newValue = objc_retain(newValue);
    }

    if (!atomic) {
        oldValue = *slot;
        *slot = newValue;
    } else {
        spinlock_t& slotlock = PropertyLocks.get()[slot];
        slotlock.lock();
        oldValue = *slot;
        *slot = newValue;        
        slotlock.unlock();
    }

    objc_release(oldValue);
}

总结

  • objc_setProperty方法的目的适用于关联 上层set方法 以及 底层set方法,其本质就是一个接口

  • 这么设计的原因是,上层set方法有很多,如果直接调用底层set方法中,会产生很多的临时变量,当你想查找一个sel时,会非常麻烦

  • 基于上述原因,苹果采用了适配器设计模式(即将底层接口适配为客户端需要的接口)对外提供一个接口,供上层的set方法使用,对内调用底层的set方法,使其相互不受影响

cls(isa_t中的一个字段)与类的关联原理

isa 的类型 isa_t

下面 isa 指针 isa_t的定义,下面是使用联合体定义的

objc 复制代码
union isa_t {
    isa_t() { } // 默认的一个构造函数
    isa_t(uintptr_t value) : bits(value) { }

    uintptr_t bits; // 这个和cls是一个互斥类型

private:
    // Accessing the class requires custom ptrauth operations, so
    // force clients to go through setClass/getClass by making this
    // private.
    Class cls; // 这里把cls指针私有化,禁止直接访问cls,必须通过getClass()和setClass()方法操作

public:
#if defined(ISA_BITFIELD)
    struct {
        ISA_BITFIELD;  // defined in isa.h 这里是一个位域设置一个结构体
    };

    bool isDeallocating() {
        return extra_rc == 0 && has_sidetable_rc == 0; //判断位域对应的一个标志位
    }
    void setDeallocating() {
        extra_rc = 0; 
        has_sidetable_rc = 0;
    }
#endif

    void setClass(Class cls, objc_object *obj); //提供set方法获取cls指针更加安全
    Class getClass(bool authenticated); //提供get方法获取cls指针
    Class getDecodedClass(bool authenticated); 。。
}; //这里是因为C++支持里一个联合体方法,c++11以上支持联合体里面放方法,这里的方法并会被存储到我们的一个代码段,不会影响联合体的一个内存

我们看一下这里面的联合体

  • 这里面提供了两个成员,clsbits,由联合体的定义所知,这两个成员是互斥的,也就意味着,当初始化isa指针时,有两种初始化方式

    • 通过cls初始化,bits无默认值
    • 通过bits初始化,cls有默认值
  • 这里提供了一个位域的内容,用于存储类信息以及其他信息,结构体的成员ISA_BITFIEld这里我们就看一下他是怎么定义位域的

    下面是源码

    这是 arm 的

objc 复制代码
#     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 unused            : 1;                                       \
        uintptr_t has_sidetable_rc  : 1;                                       \
        uintptr_t extra_rc          : 19

这是 x86 的

objc 复制代码
#   define ISA_BITFIELD                                                        \
      uintptr_t nonpointer        : 1;                                         \
      uintptr_t has_assoc         : 1;                                         \
      uintptr_t has_cxx_dtor      : 1;                                         \
      uintptr_t shiftcls          : 44; /*MACH_VM_MAX_ADDRESS 0x7fffffe00000*/ \
      uintptr_t magic             : 6;                                         \
      uintptr_t weakly_referenced : 1;                                         \
      uintptr_t unused            : 1;                                         \
      uintptr_t has_sidetable_rc  : 1;                                         \
      uintptr_t extra_rc          : 8
  • nonpointer有两个值,表示自定义的类等,占1

    • 0纯isa指针
    • 1:不只是类对象地址,isa中包含了类信息、对象的引用计数
  • has_assoc表示关联对象标志位,占1

    • 0没有关联对象
    • 1存在关联对象
  • has_cxx_dtor 表示该对象是否有C++/OC的析构器(类似于dealloc),占1

    • 如果析构函数,则需要做析构逻辑
    • 如果没有,则可以更快的释放对象
  • shiftcls表示存储类的指针的值(类的地址), 即类信息

    • arm64中占 33位,开启指针优化的情况下,在arm64架构中有33位用来存储类指针
    • x86_64中占 44
  • magic 用于调试器判断当前对象是真的对象 还是 没有初始化的空间,占6

  • weakly_refrenced是 指对象是否被指向 或者 曾经指向一个ARC的弱变量

    • 没有弱引用的对象可以更快释放
  • deallocating 标志对象是是否正在释放内存

  • has_sidetable_rc表示 当对象引用计数大于10时,则需要借用该变量存储进位

  • extra_rc(额外的引用计数) ,表示该对象的引用计数值,实际上是引用计数值减1

在上一篇博客我们[[[OC 底层] (一) alloc, init & new]]我们探讨了到了 initInstanceIsa,现在我们回看一下

objc 复制代码
objc_object::initInstanceIsa(Class cls, bool hasCxxDtor)
{
    ASSERT(!cls->instancesRequireRawIsa());
    ASSERT(hasCxxDtor == cls->hasCxxDtor());

    initIsa(cls, true, hasCxxDtor);
}

在这里面有一个很重要的方法

initIsa方法

这才是一个最终操纵内存的方法

objc 复制代码
inline void
objc_object::initIsa(Class cls, bool nonpointer, UNUSED_WITHOUT_INDEXED_ISA_AND_DTOR_BIT bool hasCxxDtor)
// cls: 要绑定的类
// nonpointer: true = 开启 nonpointer isa 优化;false = 纯类指针模式
// hasCxxDtor: 该类是否有 C++ 析构函数,释放时需要走析构逻辑
{
    ASSERT(!isTaggedPointer()); // Tagged Pointer 不走这套流程,直接拦截

    isa_t newisa(0); // 创建临时 isa,初始全零

    if (!nonpointer) {
        // ── raw isa 模式:8 字节全部用来存类指针,直接写入 cls ──
        newisa.setClass(cls, this);

    } else {
        ASSERT(!DisableNonpointerIsa);          // 确认 nonpointer isa 未被禁用
        ASSERT(!cls->instancesRequireRawIsa()); // 确认该类不强制要求 raw isa

#if SUPPORT_INDEXED_ISA
        // ── indexed isa 模式(watchOS 等特殊平台):isa 里存类数组索引而非地址 ──
        ASSERT(cls->classArrayIndex() > 0);         // 索引必须有效
        newisa.bits = ISA_INDEX_MAGIC_VALUE;         // 写入魔数,同时设置 nonpointer 和 magic 标志位
        newisa.has_cxx_dtor = hasCxxDtor;            // 写入析构器标志
        newisa.indexcls = (uintptr_t)cls->classArrayIndex(); // 存类在全局类数组中的索引

#else
        // ── 标准 nonpointer isa 模式(arm64 / x86_64 主流路径)──
        newisa.bits = ISA_MAGIC_VALUE; // 整体写入魔数,同时设置 nonpointer=1 和 magic 标志位

#   if ISA_HAS_CXX_DTOR_BIT
        newisa.has_cxx_dtor = hasCxxDtor; // 写入析构器标志(部分平台才有独立 bit)
#   endif

        newisa.setClass(cls, this); // 核心:将 cls 右移 3 位编码进 shiftcls,完成对象与类的绑定
#endif

#if ISA_HAS_INLINE_RC
        newisa.extra_rc = 1; // 初始化内联引用计数(extra_rc 存的是引用计数 - 1)
#endif
    }

    // 将构造好的 newisa 一次性写入对象的 isa 字段
    // 必须单次写入:realize 类时其他线程可能同时读取 isa,分多次写会产生数据竞争
    isa() = newisa;
}

这里的 initisa 的本质作用就是:把一块刚分配的裸内存,变成一个"知道自己是谁"的合法 OC 对象。

objc 复制代码
newisa.setClass(cls, this);

这才是 initIsa 最核心的目的所在。setClass 把传入的 Class cls 指针右移 3 位后填入 shiftcls 位域,从这一刻起,这块内存就和某个具体的类绑定在一起了。之后 runtime 发消息时,会从对象的 isa 里取出 shiftcls,还原出类指针,再去类的方法列表里查找对应的方法------整个 OC 动态派发机制的起点,就是这里。

通过setClass方法中的shiftcls来验证绑定的一个流程

我们知道 setClass 是修改 isa 中类指针的唯一入口。

下面看一下他的相关实现

objc 复制代码
inline void
isa_t::setClass(Class newCls, UNUSED_WITHOUT_PTRAUTH objc_object *obj)
{
#ifdef ISA_MASK
    // Make sure the class pointer doesn't have any bits set outside the isa
    // mask bits.
    uintptr_t newClsBits = (uintptr_t)newCls;
    if (slowpath((newClsBits & ISA_MASK) != newClsBits)) {
        _objc_fatal("Invalid class pointer %p has bits set outside of ISA_MASK", newCls);
    }
#endif

    // Match the conditional in isa.h.
#if __has_feature(ptrauth_calls) || TARGET_OS_SIMULATOR
#   if ISA_SIGNING_SIGN_MODE == ISA_SIGNING_SIGN_NONE
    // No signing, just use the raw pointer.
    uintptr_t signedCls = (uintptr_t)newCls;

#   elif ISA_SIGNING_SIGN_MODE == ISA_SIGNING_SIGN_ONLY_SWIFT
    // We're only signing Swift classes. Non-Swift classes just use
    // the raw pointer
    uintptr_t signedCls = (uintptr_t)newCls;
    if (newCls->isSwiftStable())
        signedCls = (uintptr_t)ptrauth_sign_unauthenticated((void *)newCls, ISA_SIGNING_KEY, ptrauth_blend_discriminator(obj, ISA_SIGNING_DISCRIMINATOR));

#   elif ISA_SIGNING_SIGN_MODE == ISA_SIGNING_SIGN_ALL
    // We're signing everything
    uintptr_t signedCls = (uintptr_t)ptrauth_sign_unauthenticated((void *)newCls, ISA_SIGNING_KEY, ptrauth_blend_discriminator(obj, ISA_SIGNING_DISCRIMINATOR));

#   else
#       error Unknown isa signing mode.
#   endif

    shiftcls_and_sig = signedCls >> 3;

#elif SUPPORT_INDEXED_ISA
    // Indexed isa only uses this method to set a raw pointer class.
    // Setting an indexed class is handled separately.
    cls = newCls;

#else // Nonpointer isa, no ptrauth
    shiftcls = (uintptr_t)newCls >> 3;
#endif
}

在这里我们做两个LLDB 相关的调试,来验证我们是否把类信息存储到 shiftcls 中

我们先看输入
po (Class)newCls ------看输入

我们输入类的地址

然后我们看输出的类的地址和输入是否一致
p/x newisa.bits & 0x00007ffffffffff8ULL ------看输出

得到的结论是一致的所以我们是把相关的类信息存储到了 shiftcls 中。

在后面还有三个方法
方法二:object_getClass

通过 runtime 提供的 API 从对象身上取出类,不需要手动做位运算,runtime 内部帮你做了 ISA_MASK 的处理

objc 复制代码
po object_getClass((id)obj)

方法三:po (Class)newCls 直接打印

setClass 执行之前,直接打印 newCls 确认传入的类是否正确
方法四:位运算手动计算(不依赖调试器)​

纯数学推导,不需要断点,直接用已知地址计算:

复制代码
原始类地址:0x00000001005d90a0
右移 3 位后存入 shiftcls:0x00000001005d90a0 >> 3 = 0x000000020 0bb2140
还原:shiftcls << 3 = 0x00000001005d90a0  
复制代码
[Person alloc]
    ↓
cls = Person 的内存地址(早已存在)
    ↓
calloc → mem(全零裸内存)
    ↓
initIsa(cls=Person, this=mem)
    ├─ newisa(0)            全零草稿
    ├─ bits = MAGIC_VALUE   写标志位
    ├─ setClass(Person)     shiftcls = Person >> 3
    ├─ extra_rc = 1         引用计数
    └─ isa = newisa         草稿写入 mem 的头部
    ↓
alloc 返回 mem
    ↓
p = mem   ← p->isa->shiftcls 里存着 Person 的地址

类&类结构的分析

类的分析

类的分析 主要是分析 isa的走向 以及 继承关系

objc 复制代码
@interface Person : NSObject
{
  NSString *hobby;
}
@property (nonatomic, copy) NSString *LK_Name;
- (void) sayHello;
+ (void) sayBye;
@end

@implementation Person
- (void) sayHello {
}
+ (void) sayBye {
}
@end

@interface Teacher : Person
@end

@implementation Teacher
@end

下面会有一段终端命令

如果说是理解实例->类对象->元类->根元类的话最核心的应该是这几个

这里你可以发下主要调试命令是这几个 x/4gx,p/x,po
p/x 表达式以十六进制打印
po 对象打印 OC 对象的描述
x/4gx 地址读取地址起始的 4 个 8 字节内存块

好的我们详细说一下上面的这几个操作,从第一步开始,

复制代码
x/4gx person
0xae1002560: 0x00000001000083f0 0x0000000000000000
0xae1002570: 0x0000000000000000 0x0000000000000000

我们的核心目的是获得 person 的 isa 指针0x00000001000083f0,然后通过 isa 指针获得他的类也就是类对象的指针,

复制代码
p/x 0x00000001000083f0 & 0x0000000ffffffff8ULL

(unsigned long long) 0x00000001000083f0

获得类指针后我么继续获得他的相关 isa 指针

复制代码
x/4gx 0x00000001000083f0

0x1000083f0: 0x00000001000083c8 0x00000001fd72ed58

0x100008400: 0x00000001005c2840 0xe021000000000000

对 Person 类的 isa 做 mask,得到元类地址,

复制代码
p/x 0x00000001000083c8 & 0x0000000ffffffff8ULL

(unsigned long long) 0x00000001000083c8

下来我们介绍一下什么是元类

元类

    • 我们都知道 对象isa 是指向的其实也是一个对象,可以称为类对象,其isa的位域指向苹果定义的元类
  • 元类是系统给的,他的定义和创建都是由编译器玩测的,这个过程中,类的归属来自于元类
  • 元类是类对象的类,每一个类都有独一无二的元类来存储类方法的相关信息
  • 元类本身是没有名字的,只是因为类与之相关联,所以直接采用了类的一个名字
    下面我们通过 LLDB 命令来展示出完整的一个流程

这里比上面多了一轮我么可以发现,这多的一轮就是在寻找根元类,所以最后的类的地址就是 NSObject

总结
  • 对象isa 指向 (也可称为类对象
  • isa 指向 元类
  • 元类isa 指向 根元类,即NSObject
  • 根元类isa 指向 它自己


isa走位

isa的走向有以下几点说明:

  • 实例对象(Instance of Subclass)isa 指向 类(class)

  • 类对象(class) isa 指向 元类(Meta class)

  • 元类(Meta class)isa 指向 根元类(Root metal class)

  • 根元类(Root metal class)isa 指向它自己本身,形成闭环,这里的根元类就是NSObject

superclass走位

superclass(即继承关系)的走向也有以下几点说明:

  • 之间 的继承关系:

    • 类(subClass) 继承自 父类(superClass)

    • 父类(superClass) 继承自 根类(RootClass),此时的根类是指NSObject

    • 根类 继承自 nil,所以根类NSObject可以理解为万物起源,即无中生有

  • 元类也存在继承,元类之间的继承关系如下:

    • 子类的元类(metal SubClass) 继承自 父类的元类(metal SuperClass)

    • 父类的元类(metal SuperClass) 继承自 根元类(Root metal Class

    • 根元类(Root metal Class) 继承于 根类(Root class),此时的根类是指NSObject

objc_class & objc_object

isa走位我们理清楚了,又来了一个新的问题:为什么 对象都有isa属性呢?这里就不得不提到两个结构体类型:objc_class & objc_object

NSObject的底层编译是NSObject_IMPL结构体,

  • 其中 Classisa指针的类型,是由objc_class定义的类型,
  • objc_class是一个结构体。在iOS中,所有的Class都是以 objc_class 为模板创建的
c 复制代码
struct NSObject_IMPL {
    Class isa;
};
typedef struct objc_class *Class;

下面是 objc_class 的结构体

objc 复制代码
struct objc_class : objc_object {
  objc_class(const objc_class&) = delete;
  objc_class(objc_class&&) = delete;
  void operator=(const objc_class&) = delete;
  void operator=(objc_class&&) = delete;
    // Class ISA;
    Class superclass;
    cache_t cache;             // formerly cache pointer and vtable
    class_data_bits_t bits;    // class_rw_t * plus custom rr/alloc flags

    Class getSuperclass() const {
    ···代码太多了

下面是 objc_object结构体

objc 复制代码
struct objc_object {
private:
    char isa_storage[sizeof(isa_t)];

    isa_t &isa() { return *reinterpret_cast<isa_t *>(isa_storage); }
    const isa_t &isa() const { return *reinterpret_cast<const isa_t *>(isa_storage); }

public:

    // ISA() assumes this is NOT a tagged pointer object
    Class ISA(bool authenticated = false) const;

    // rawISA() assumes this is NOT a tagged pointer object or a non pointer ISA
    Class rawISA() const;

    // getIsa() allows this to be a tagged pointer object
    Class getIsa() const;
    
    uintptr_t isaBits() const;

    // initIsa() should be used to init the isa of new objects only.
    // If this object already has an isa, use changeIsa() for correctness.
    // initInstanceIsa(): objects with no custom RR/AWZ
    // initClassIsa(): class objects
    // initProtocolIsa(): protocol objects
    // initIsa(): other objects
    void initIsa(Class cls /*nonpointer=false*/);
    void initClassIsa(Class cls /*nonpointer=maybe*/);
    void initProtocolIsa(Class cls /*nonpointer=maybe*/);
    void initInstanceIsa(Class cls, bool hasCxxDtor);

    // changeIsa() should be used to change the isa of existing objects.
    // If this is a new object, use initIsa() for performance.
    Class changeIsa(Class newCls);

    bool hasNonpointerIsa() const;
    bool isTaggedPointer() const;
    bool isBasicTaggedPointer() const;
    bool isExtTaggedPointer() const;
    bool isClass() const;

    // object may have associated objects?
    bool hasAssociatedObjects() const;
    void setHasAssociatedObjects();
    ···代码太多了不展示了
objc_class与 objc_object有什么关系?
  • 结构体类型objc_class 继承自objc_object类型,其中objc_object也是一个结构体,且有一个isa属性,所以objc_class也拥有了isa属性

  • mian.cpp底层编译文件中,NSObject中的isa在底层是由Class 定义的,其中class的底层编码来自 objc_class类型,所以NSObject也拥有了isa属性

  • NSObject 是一个类,用它初始化一个实例对象objc,objc 满足 objc_object 的特性(即有isa属性),主要是因为isa 是由 NSObjectobjc_class继承过来的,而objc_class继承自objc_objectobjc_objectisa属性。所以对象都有一个 isa,isa表示指向,来自于当前的objc_object

  • objc_object(结构体) 是 当前的 根对象所有的对象都有这样一个特性 objc_object,即拥有isa属性

objc_object与对象的关系
  • 所有的对象 都是以 objc_object为模板继承过来的

  • 所有的对象 是 来自 NSObject(OC) ,但是真正到底层的 是一个objc_object(C/C++)的结构体类型

总结
  • 所有的对象 + + 元类 都有isa属性

  • 所有的对象都是由objc_object继承来的

  • 简单概括就是万物皆对象,万物皆来源于objc_object,有以下两点结论:

    • 所有以 objc_object为模板 创建的对象,都有isa属性

    • 所有以objc_class为模板,创建的,都有isa属性

  • 在结构层面可以通俗的理解为上层OC底层对接

    • 下层是通过 结构体 定义的 模板,例如objc_class、objc_object
    • 上层 是通过底层的模板创建的 一些类型,例如Person

objc_class、objc_object、isa、object、NSObject等的整体的关系,如下图所示

类结构分析

主要分析类信息中储存类哪些内容

探索类信息中有什么时,事先我们并不清楚类结构是什么样的,但是我们可以通过得到一个首地址,然后通过地址平移去获取里面所有的值

下面是 objc_class的结构体,里面有一下几个属性

objc 复制代码
struct objc_class : objc_object {
  objc_class(const objc_class&) = delete;
  objc_class(objc_class&&) = delete;
  void operator=(const objc_class&) = delete;
  void operator=(objc_class&&) = delete;
    // Class ISA;
    Class superclass;
    cache_t cache;             // formerly cache pointer and vtable
    class_data_bits_t bits;
  • isa属性:继承自objc_objectisa,占 8字节

  • superclass 属性:Class类型,Class是由objc_object定义的,是一个指针,占8字节

  • cache属性:简单从类型class_data_bits_t目前无法得知,而class_data_bits_t是一个结构体类型,结构体内存大小需要根据内部的属性来确定,而结构体指针才是8字节

  • bits属性:只有首地址经过上面3个属性的内存大小总和的平移,才能获取到bits

计算cache类的内存大小

进入cache类cache_t的定义(只贴出了结构体中非static修饰的属性,主要是因为static类型的属性 不存在结构体的内存中)

objc 复制代码
struct cache_t {
private:
    explicit_atomic<uintptr_t> _bucketsAndMaybeMask;
    union {
        // Note: _flags on ARM64 needs to line up with the unused bits of
        // _originalPreoptCache because we access some flags (specifically
        // FAST_CACHE_HAS_DEFAULT_CORE and FAST_CACHE_HAS_DEFAULT_AWZ) on
        // unrealized classes with the assumption that they will start out
        // as 0.
        struct {
#if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_OUTLINED && !__LP64__
            // Outlined cache mask storage, 32-bit, we have mask and occupied.
            explicit_atomic<mask_t>    _mask;
            uint16_t                   _occupied;
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_OUTLINED && __LP64__
            // Outlined cache mask storage, 64-bit, we have mask, occupied, flags.
            explicit_atomic<mask_t>    _mask;
            uint16_t                   _occupied;
            uint16_t                   _flags;
#   define CACHE_T_HAS_FLAGS 1
#elif __LP64__
            // Inline cache mask storage, 64-bit, we have occupied, flags, and
            // empty space to line up flags with originalPreoptCache.
            //
            // Note: the assembly code for objc_release_xN knows about the
            // location of _flags and the
            // FAST_CACHE_HAS_CUSTOM_DEALLOC_INITIATION flag within. Any changes
            // must be applied there as well.
            uint32_t                   _disguisedPreoptCacheSignature;
            uint16_t                   _occupied;
            uint16_t                   _flags;

cache_t 总是 16 字节,不管哪个平台。
第一个字段 _bucketsAndMaybeMask ,所有平台都是 uintptr_t,固定 8 字节,没有分歧。
第二个字段union

  • 32 位 OUTLINED:_mask(4)+ _occupied(2)= 6 字节,对齐到 8
  • 64 位 OUTLINED:_mask(4)+ _occupied(2)+ _flags(2)= 恰好 8 字节
  • 64 位内联(arm64 真机):_disguisedPreoptCacheSignature(4)+ _occupied(2)+ _flags(2)= 恰好 8 字节

联合体的大小取最大成员,最大就是 8 字节,所以联合体固定 8 字节。

获取bits

bits:封装了类的其他信息,例如成员变量,方法列表,协议,属性

这里我们先来认识bits中非常重要的一个类别class_rw_t这是一个结构体类型,这里目前什么都没有需要我们进行进一步探索

objc 复制代码
class_rw_t* data() const {
        ASSERT(has_rw_pointer());
        uintptr_t localBits = bits.load(std::memory_order_relaxed);
#if __BUILDING_OBJCDT__
        return (class_rw_t *)((uintptr_t)ptrauth_strip((class_rw_t *)localBits,
                                                           CLASS_DATA_BITS_RW_SIGNING_KEY) & FAST_DATA_MASK);
#else
        return (class_rw_t *)((uintptr_t)ptrauth_auth_data((class_rw_t *)localBits,
                                                           CLASS_DATA_BITS_RW_SIGNING_KEY,
                                                           ptrauth_blend_discriminator(&bits,
                                                                                       CLASS_DATA_BITS_RW_DISCRIMINATOR)) & FAST_DATA_MASK);
#endif

    }
objc 复制代码
 void setData(class_rw_t *newData)
    {
        ASSERT(!has_rw_pointer()
               || (newData->flags & (RW_REALIZING | RW_FUTURE)));

        uintptr_t authedBits;

        uintptr_t localBits = bits.load(std::memory_order_relaxed);

        if (objc::disableEnforceClassRXPtrAuth) {
            authedBits = (uintptr_t)ptrauth_strip((const void *)localBits,
                                                  CLASS_DATA_BITS_RW_SIGNING_KEY);
        } else {
            if (localBits == 0) {
                authedBits = 0;
            } else if (has_rw_pointer(localBits)) {
                authedBits = (uintptr_t)ptrauth_auth_data((class_rw_t *)localBits,
                                                          CLASS_DATA_BITS_RW_SIGNING_KEY,
                                                          ptrauth_blend_discriminator(&bits,
                                                                                      CLASS_DATA_BITS_RW_DISCRIMINATOR));
            } else {
                authedBits = (uintptr_t)ptrauth_auth_data((class_ro_t *)localBits,
                                                          CLASS_DATA_BITS_RO_SIGNING_KEY,
                                                          ptrauth_blend_discriminator(&bits,
                                                                                      CLASS_DATA_BITS_RO_DISCRIMINATOR));
            }
        }

        // Set during realization or construction only. No locking needed.
        // Use a store-release fence because there may be concurrent
        // readers of data and data's contents.
        uintptr_t newBits = ((authedBits & FAST_FLAGS_MASK)
                             | (uintptr_t)newData
                             | FAST_IS_RW_POINTER);
        class_rw_t *signedData
            = ptrauth_sign_unauthenticated((class_rw_t *)newBits,
                                           CLASS_DATA_BITS_RW_SIGNING_KEY,
                                           ptrauth_blend_discriminator(&bits,
                                                                       CLASS_DATA_BITS_RW_DISCRIMINATOR));
        bits.store((uintptr_t)signedData, std::memory_order_release);
    }
objc 复制代码
const class_ro_t *safe_ro() const {
        // Load bits once, then work on that value. This prevents us from seeing
        // !has_rw_pointer, then concurrently a class_rw_t pointer is stored,
        // then we try to authenticate it using the RO signing scheme, or return
        // a class_rw_t* as a class_ro_t* when ptrauth is not enabled.
        uintptr_t bitsValue = bits.load(std::memory_order_relaxed);
        if (has_rw_pointer(bitsValue)) {
            return data()->ro();
        }

        uintptr_t authedBits;
        if (authentication == Authentication::Strip || objc::disableEnforceClassRXPtrAuth) {
            authedBits = (uintptr_t)ptrauth_strip((const void *)bitsValue,
                                                  CLASS_DATA_BITS_RO_SIGNING_KEY);
        } else {
            authedBits = (uintptr_t)ptrauth_auth_data((const void *)bitsValue,
                                                      CLASS_DATA_BITS_RO_SIGNING_KEY,
                                                      ptrauth_blend_discriminator(&bits,
                                                                                  CLASS_DATA_BITS_RO_DISCRIMINATOR));
        }

        return (const class_ro_t *)(authedBits & FAST_DATA_MASK);
    }

在这里有 bits 中最重要的两个方法,一个是safe_ro它返回的是 class_ro_t 另一个是data返回的是 class_rw_t

探索property_list(属性列表 )

通过查看class_rw_t这个类.我们可以发现这里的一个属性列表和方法列表等多个列表

这里的存储在 class_rw_t这个类是储存一个属性的,不储存成员变量和实例变量

成员变量存在 class_ro_t

objc 复制代码
struct class_ro_t {
    uint32_t flags;
    uint32_t instanceStart;
    uint32_t instanceSize;
#ifdef __LP64__
    uint32_t reserved;
#endif

    union {
        const uint8_t * ivarLayout;
        Class nonMetaclass;
    };

    explicit_atomic<const char *> name;
    objc::PointerUnion<method_list_t, relative_list_list_t<method_list_t>, method_list_t::Ptrauth, method_list_t::Ptrauth> baseMethods;
    objc::PointerUnion<protocol_list_t, relative_list_list_t<protocol_list_t>, PtrauthRaw, PtrauthRaw> baseProtocols;
    const ivar_list_t * ivars;

    const uint8_t * weakIvarLayout;
    objc::PointerUnion<property_list_t, relative_list_list_t<property_list_t>, PtrauthRaw, PtrauthRaw> baseProperties;

    // This field exists only when RO_HAS_SWIFT_INITIALIZER is set.
    _objc_swiftMetadataInitializer __ptrauth_objc_method_list_imp _swiftMetadataInitializer_NEVER_USE[0];

    _objc_swiftMetadataInitializer swiftMetadataInitializer() const {
        if (flags & RO_HAS_SWIFT_INITIALIZER) {
            return _swiftMetadataInitializer_NEVER_USE[0];
        } else {
            return nil;
        }
    }

    const char *getName() const {
        return name.load(std::memory_order_acquire);
    }

    class_ro_t *duplicate() const {
        bool hasSwiftInitializer = flags & RO_HAS_SWIFT_INITIALIZER;

        size_t size = sizeof(*this);
        if (hasSwiftInitializer)
            size += sizeof(_swiftMetadataInitializer_NEVER_USE[0]);

        class_ro_t *ro = (class_ro_t *)memdup(this, size);

        if (hasSwiftInitializer)
            ro->_swiftMetadataInitializer_NEVER_USE[0] = this->_swiftMetadataInitializer_NEVER_USE[0];

#if __has_feature(ptrauth_calls)
        // Re-sign the method list pointer.
        ro->baseMethods = baseMethods;
#endif

        return ro;
    }

    Class getNonMetaclass() const {
        ASSERT(flags & RO_META);
        return nonMetaclass;
    }

    const uint8_t *getIvarLayout() const {
        if (flags & RO_META)
            return nullptr;
        return ivarLayout;
    }
};

这里面有方法,属性,协议还有成员变量,但方法,属性,协议的开头是用base开头的。

我们想要获得成员变量列表,这个时候需要用到ivars这个列表包含的内容不仅仅包含一个成员变量列表,除了包括在{}中定义的一个成员变量,还包括通过属性定义的成员变量.bits --> data() -->ro() --> ivars通过这个流程来获取成员变量表。
its --> data() --> ro() --> ivars 这条访问链说明,成员变量列表 ivars 存储在 class_ro_t 里,必须经过 bitsclass_rw_t(通过 data())→ class_ro_t(通过 ro())才能拿到。

通过@property定义的属性,也会存储在bits属性中,通过bits --> data() --> properties() --> list获取属性列表,其中只包含属性。

探索methods_list

这里的 methods_list是不储存方法的,他只储存实例方法,存在类中,

复制代码
Person类 --> bits --> methods() --> list

我们的类方法存储在元类的 bits里,

复制代码
Person元类 --> bits --> methods() --> list
相关推荐
ZZH_AI项目交付15 小时前
扫脸功能交给 SDK 后,主工程里的旧代码怎么删除
ios·app·apple
ZZH_AI项目交付17 小时前
扫脸功能做成 SDK,为什么我没有把结果页和历史记录一起搬进去
ios·app
茶底世界之下17 小时前
诡异!String 参数在闭包里变成了 <uninitialized>,我排查了整整两天
ios·xcode·swift
谢斯18 小时前
[esp-idf]macos 环境安装 v6.0
macos
harder32119 小时前
iOS IPA 马甲包送审风险评估工具
ios
idolao20 小时前
ANSYS 2024安装教程 Windows版:License Manager配置+环境变量+Fluent汉化指南
macos
SameX20 小时前
存钱 App 开发手记:restitution 0.3 是怎么试出来的,以及 86400 秒不等于一天
ios
Digitally1 天前
4 种方法将 Mac 联系人同步到 iPhone
macos·cocoa·iphone
for_ever_love__1 天前
UI学习:反向传值(代理传值)深入学习
学习·ui·objective-c