OC底层原理04:isa走向&类结构分析

495 阅读6分钟

类 isa 走向分析

元类

OC 中类也是一个对象,它的指针也会指向它所属的类,这样的类就是元类(MetaClass).元类的定义和创建,都是由编译器自动完成的.

类在底层都会被编译为结构体,所有的结构体都“继承”了 NSObject 结构体的Class isa,所以所有的类都包含了 isa,isa 指针指向了其所属的类.实例对象的 isa 指向&归属于类对象,类对象的 isa 指向&归属它的元类.实例对象->类对象->元类

isa 走向

上篇文章,我们看到 alloc 出来的实例对象中,isa 位域 shiftcls 保存的就是类的指针值,我们深入看下,类的 isa 指针,都指向了哪里呢?

//Code
@interface LGPerson : NSObject
@property(nonatomic, copy)NSString *kc_name;
- (void)setNB;
@end

@implementation LGPerson
- (void)setNB{

}
@end


LGPerson *person = [LGPerson alloc];

// DeBug
(lldb) x/4gx person // 实例对象
0x10182cc50: 0x001d8001000021c9 0x0000000000000000
0x10182cc60: 0x63756f54534e5b2d 0x6f6c6f4372614268
(lldb) p/x 0x001d8001000021c9 & 0x00007ffffffffff8ULL
(unsigned long long) $67 = 0x00000001000021c8
(lldb) po 0x00000001000021c8
LGPerson // 类(类对象)

(lldb) x/4gx 0x00000001000021c8
0x1000021c8: 0x00000001000021a0 0x0000000100334140
0x1000021d8: 0x000000010032e430 0x0000801000000000
(lldb) po 0x00000001000021a0
LGPerson // 元类

(lldb) x/4gx 0x00000001000021a0
0x1000021a0: 0x00000001003340f0 0x00000001003340f0
0x1000021b0: 0x0000000101831790 0x0004e03100000007
(lldb) po 0x00000001003340f0
NSObject // 根元类

(lldb) x/4gx 0x00000001003340f0 // 根元类的isa指向自己
0x1003340f0: 0x00000001003340f0 0x0000000100334140
0x100334100: 0x0000000100640570 0x0005e03100000007
(lldb)


  • 那么我们再看下,继承 LGPerson 的类 LGTercher,它的 isa 都指向了哪里呢?
// Code
@interface LGTercher : LGPerson
@end

@implementation LGTercher
@end

// DeBug
(lldb) x/4gx tercher  // 实例对象的内存分布
0x101c2c910: 0x001d800100002179 0x0000000000000000
0x101c2c920: 0x50626154534e5b2d 0x65695672656b6369
(lldb) p/x 0x001d800100002179 & 0x00007ffffffffff8ULL
(unsigned long long) $12 = 0x0000000100002178
(lldb) po 0x0000000100002178 // 实例对象的isa指向了类LGTercher
LGTercher

(lldb) x/4gx 0x0000000100002178 // 类LGTercher的内存分布
0x100002178: 0x0000000100002150 0x00000001000021c8
0x100002188: 0x000000010032e430 0x0000801000000000
(lldb) po 0x0000000100002150 // 类的isa指向了它的元类
LGTercher

(lldb) x/4gx 0x0000000100002150 // 元类的内存分布
0x100002150: 0x00000001003340f0 0x00000001000021a0
0x100002160: 0x0000000101c36480 0x0003e03100000007
(lldb) po 0x00000001003340f0 // 元类的isa指向了根元类
NSObject

(lldb) x/4gx 0x00000001003340f0
0x1003340f0: 0x00000001003340f0 0x0000000100334140
0x100334100: 0x0000000101c36870 0x0004e03100000007
(lldb)

由 LGTercher 的调试结果,我们看到,元类 LGTercher 的 isa 直接指向了根元类,并不根据继承关系指向 LGPerson.所以所有的元类都会直接指向根元类.


经典的走向图(继承:实线,isa指向:虚线):

流程图探究

  1. 所有的类都会指向它的元类,类归属于它的元类,包括根类 NSObject
  2. 所有的元类都直接指向了根元类,根元类也会指向自己
  3. 继承关系存在于类与类,包括元类与元类之间,实例对象之间是没有关系的
  4. 类的继承关系,最终都继承自 NSObject,包括根元类也继承自根类,而根类继承于 nil,无中生有

面试题:类在内存中存在几份?

答案是一份.虽然类归属于元类,但类的信息在编译后只会存在一份


类结构分析

之前利用Clang编译得到的main.cpp文件,我们可以看到,类编译后的结果都是结构体,而每个结构体都会"继承"自NSObject_IMPL结构体,而Class类型表示了指向objc_class结构体的指针

struct NSObject_IMPL {
 Class isa;
};

typedef struct objc_class *Class;

这时在cpp文件中,点不到objc_class结构体,我们再来到781版本的objc源码Source Browser全局搜一下。

在objc-runtime-new.h文件中的objc_class是最新的,并且它继承自objc_object

  • objc_class:表示类在编译后的结构体,其中第一个成员数据是继承自父类的isa指针
  • superclass:表示当前类继承的父类
  • cache:利用散列表来缓存调用过的方法,提交访问的速度
  • bits:Class的核心信息

objc_object源码:


bits类信息探索

由objc_class的结构体源码,我们可以通过类的首地址偏移,来获取到bits中的信息。

首地址

x/4gx LGPerson.class
0x100002250: 0x0000000100002228 0x0000000100334140
0x100002260: 0x000000010032e420 0x0000802400000000

LGPerson类的首地址为0x100002250


偏移地址

  • ISAsuperclass都是objc_class的结构体指针,一共16字节。
  • cache中
// cache关键部分,static&方法()不在计算范围内
struct cache_t {
#if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_OUTLINED
    explicit_atomic<struct bucket_t *> _buckets;
    explicit_atomic<mask_t> _mask;
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16
    explicit_atomic<uintptr_t> _maskAndBuckets;
    mask_t _mask_unused;

#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_LOW_4
    // _maskAndBuckets stores the mask shift in the low 4 bits, and
    // the buckets pointer in the remainder of the value. The mask
    // shift is the value where (0xffff >> shift) produces the correct
    // mask. This is equal to 16 - log2(cache_size).
    explicit_atomic<uintptr_t> _maskAndBuckets;
    mask_t _mask_unused;
    
    ...

#endif
#if __LP64__
    uint16_t _flags;
#endif
    uint16_t _occupied;
    
    ...
  1. struct bucket_t *类型是结构体指针,8字节
  2. typedef uint32_t mask_t;类型是4字节
  3. typedef unsigned long uintptr_t;类型是8字节
  4. uint16_t类型是2字节

所以cache所占内存一共12+2+2=16字节,最终的偏移量为32字节


获取bits内容

/* Debug
   首地址为:0x100002250 偏移量为32字节, 
   class_data_bits_t bits的地址就在0x100002270 (16进制)
*/
p (class_data_bits_t *)0x100002270
// (class_data_bits_t *)$3 = 0x00000000100002270
p $3->data()  // 调用bits中的data()方法
//(class_rw_t *)$4 = 0x0000000100b19bf0
p $4
/*
  (class_rw_t) $6 = {
    flags = 2148007936
    witness = 0
    ro_or_rw_ext = {
      std::_ _1::atomic<unsigned long> = 4294975616
    }
    firstSubclass = LGTercher
    nextSiblingClass = NSUUID
  }
*/

$6都是些啥玩意儿啊??!!

我们点到bits.data()方法的返回类型class_rw_t

struct class_rw_t {
  
  ...
  
    const method_array_t methods() const {
        auto v = get_ro_or_rwe();
        if (v.is<class_rw_ext_t *>()) {
            return v.get<class_rw_ext_t *>()->methods;
        } else {
            return method_array_t{v.get<const class_ro_t *>()->baseMethods()};
        }
    }

    const property_array_t properties() const {
        auto v = get_ro_or_rwe();
        if (v.is<class_rw_ext_t *>()) {
            return v.get<class_rw_ext_t *>()->properties;
        } else {
            return property_array_t{v.get<const class_ro_t *>()->baseProperties};
        }
    }
}

一直看到最后,看到了method_array_t methods()property_array_t properties()两个方法。

  • 获取properties

输出后我们看到,property_list_t中的属性数据count有一条,属性的名称为kc_name,和LGPerson中是相符的。


  • 获取methods(重新Debug,$4和上边$6一样,都是class_rw_t数据)

此时我们可以看到,methods中一共有4个方法

(method_t) $9 = {
  name = "sayNB"
  ...
}
(method_t) $10 = {
  name = "kc_name"
  ...
}
(method_t) $11 = {
  name = "setKc_name:"
  ...
}
(method_t) $12 = {
  name = ".cxx_destruct"
  ...
}
  1. 类中添加的sayNB方法
  2. 属性的set&get方法会在编译过程中自动生成
  3. oc封装于C++底层,所以会默认添加destruct方法

总结:

  • 实例对象的isa指向类对象,类对象isa指向它的元类,元类isa指向根元类,根元类isa指向自己
  • 类最终都继承了根类NSObject,根类继承nil
  • 根类的isa指向根元类,根元类继承自根类
  • 类的结构体包含isa、superclass、cache、bits成员数据
  • 类的属性&实例函数都存在类结构体的class_data_bits_t bits

推荐参考

探究 cache_t (方法缓存)的本质

类 & 类结构分析

本文使用 mdnice 排版