OC对象的底层结构及isa、superClass详解

OC对象的底层结构及isa、superClass详解_第1张图片
  • isa指针
    通过上一篇文章的分析我们已经知道了实例对象,类对象,元类对象的结构如上图所示,每个对象中都有 isa 指针,isa 指针有什么作用?他们之间的关系是怎样的呢?我们写一个Person类然后调用它的实例方法eat,看一下底层源码:
[person eat];
//本质
objc_msgSend(person, sel_registerName("eat"));

[person eat]本质就是向person对象发送一条eat消息,通过上面的结构图我们知道,对象方法是存放在类对象中的,并不在实例对象中,那实例对象是怎么拿到类对象中的对象方法的呢?同样,类方法也是如此怎么拿到存放在元类对象中的类方法的呢?.其实这就用到了isa指针,大家记住:实例对象的isa指针指向类对象,类对象的isa指针指向元类对象,元类对象的isa指针指向基类的元类对象

  • superClass
    superClass 只存在类对象和元类对象中,实例对象中没有superClass.类对象的 superClass 指向父类的类对象,父类的 superClass 指向基类的类对象,如果基类没有父类,superClass 为nil;元类的 superClass 指向父类的元类对象,父类的元类对象的 superClass 指向基类的元类对象,基类的元类对象的 superClass 指向基类.
    现在我们再添加一个Student类继承自Person类,如下:
// Person
@interface Person : NSObject 
{
int _age;
int _no;
int _height;
}

-(void)personInstanceMethod;
+(void)personClassMethod;

@end

@implementation Person

- (void)personInstanceMethod{
    
}

+ (void)personClassMethod{
    
}
@end

// Student
@interface Student : Person 
{
    int _sex;
}

-(void)studentInstanceMethod;
+(void)studentClassMethod;

@end

@implementation Student

- (void)studentInstanceMethod{
    
}

+ (void)studentClassMethod{
    
}
@end

然后再创建一个student对象,让student对象去调用Person的实例方法:

Student *student = [[Student alloc]init];
[student personInstanceMethod];

我们知道Person的实例方法是存放在Person的类对象中的,student对象如何找到这个方法呢?我们画一张图:

OC对象的底层结构及isa、superClass详解_第2张图片

首先 student根据 isa找到 Student class对象,然后再通过 Student class对象的 superClass找到父类 Person class对象,再从 Person class对象中找到 Person的实例方法.如果父类 Person class中依然没有,就依次网上找,直到 NSObject,如果 NSObject中还没有,就会报一个很经典的错误 unrecognized selector sent to instance.
我们再改变一下,让改一下代码,让 Student去执行 Person的类方法:

[Student personClassMethod];

这次方法执行的查找顺序是怎样的呢?我就不画图了,其实跟[student personInstanceMethod]差不多.首先Student类对象先通过isa找到Student 元类对象,然后通过Student 元类对象superClass找到父类的元类对象,也就是Person 元类对象,然后在Person 元类对象找到类方法执行.
关于isa 和 superClass的关系,网上有人总结了一张图,可以很明显的展示,虚线表示 isa , 实现 表示 superClass:

OC对象的底层结构及isa、superClass详解_第3张图片

我们参照这张图,对isa 和 superClass做一个总结:

  • instance 的 isa 指向 class , class 的 isa 指向 meta-class , meta-class 的 isa 指向基类的 meta-class.
  • class 的 superClass 指向父类
    如果没有父类, superClass 为 nil.
  • meta-class 的 superClass 指向父类的 meta-class
    基类的 meta-class 的 superClass 指向基类的 class.
  • instance 调用方法的轨迹
    通过 isa 找到 class ,如果方法不存在就通过 superClass 找父类
  • class 调用类的方法轨迹
    通过 isa 找到 meta-class,如果没有,通过 superClass 找到父类的 meta-class.

注意上图中有一根 superClass 线我用红色的⭕️标识出来了,这根线特别特殊:基类的元类对象的 superClass 指向基类.下面我们通过代码验证一下.
我们给NSObject创建一个分类NSObject+test,然后在分类的.m方法中写一个实例方法,打印输出调用者:

- (void)test{
    NSLog(@"NSObject 的对象方法test,调用者: %p",self);
}

然后在Person类的头文件中声明一个+ (void)test;方法,在让Person调用这个方法,如下:

NSLog(@"Student 地址: %p",[Student class]);
NSLog(@"Person 地址: %p",[Person class]);
[Student test];

我们来分析一下,Person的头文件中声明了一个test方法,并没有在实现文件中实现,而NSObject+test.m文件中实现了一个对象方法test,如果我们执行[Student test];会执行成功么?
运行一下看看:

2019-02-27 15:23:19.834089+0800 OC对象的分类_01[2112:300025] Student 地址: 0x100001478
2019-02-27 15:23:19.834469+0800 OC对象的分类_01[2112:300025] Person 地址: 0x100001428
2019-02-27 15:23:19.834505+0800 OC对象的分类_01[2112:300025] NSObject 的对象方法test,调用者: 0x100001478

会发现并没有报unrecognized selector sent to instance这个错误,居然调用成功了!很奇怪,为什么我们使用Student调用➕方法,最后却执行了NSObject的➖方法呢?这就是我在上图中用红⭕️标识的哪条线的作用.

我们分析一下它的方法调用轨迹:
1: 首先Student通过isa指针找到Studentmeta-class,在Studentmeta-class中查找test方法,结果没找到.
2: 又通过Studentmeta-class中的superClass找到他的父类的元类对象,也就是Personmeta-class,结果又没找到
3: 继续通过Personmeta-class中的superClass找到NSObjectmeta-class,结果还没找到
4: 最后NSObjectmeta-classsuperClass指向了基类NSObject,它又去NSObject中查找,结果找到了- (void)test,就执行了.

大家可能会疑惑,问什么调用的类方法,最后却执行了对象方法?
因为OC调用方法的本质就是发送消息,[Student test]本质就是objc_msgSend(objc_getClass("Student"), sel_registerName("test"));它只知道去执行test,并不关心是加号方法还是减号方法.

我们一直在说实例对象的isa指针指向类对象,类对象的isa指针指向元类对象,也就是说实例对象的isa地址和类对象的地址相同,类对象的isa地址和元类的地址相同,我们来验证一下.
1: 分别创建Person的实例对象,类对象,元类对象:

//实例对象
Person *person = [[Person alloc]init];
//类对象
Class personClass = [person class];
//元类对象
Class personMetaClass = object_getClass(personClass);

2: 通过命令行打印person->isapersonClass地址

OC对象的底层结构及isa、superClass详解_第4张图片

person->isa地址是: 0x001d800100001429
personClass地址是: 0x0000000100001428
咦~怎么不一样呢?
这里需要注意一下,在 arm64 位之前,实例对象的 isa 地址和类对象的地址就是相同的,但是 arm64 位之后,实例对象的 isa 地址需要按位与 ISA_MASK后才能得到类对象的地址,我们在 object之isa指针详解篇幅中详细讲解过,有兴趣的同学可以看一下.
ISA_MASK是什么呢?打开 runtime 源码搜索一下就能看到:

# if __arm64__
#   define ISA_MASK        0x0000000ffffffff8ULL
#   define ISA_MAGIC_MASK  0x000003f000000001ULL
#   define ISA_MAGIC_VALUE 0x000001a000000001ULL

# elif __x86_64__
#   define ISA_MASK        0x00007ffffffffff8ULL
#   define ISA_MAGIC_MASK  0x001f800000000001ULL
#   define ISA_MAGIC_VALUE 0x001d800000000001ULL

如果是 arm64 位系统,ISA_MASK就是0x0000000ffffffff8ULL,如果是x86_64系统ISA_MASK就是0x00007ffffffffff8ULL,因为我们是在mac 上运行,所以我们就使用x86_64的:

计算结果

可以发现,通过位运算后得到的地址 0x0000000100001428personClass地址: 0x0000000100001428就是同一个地址!
下面验证 类对象的 isa 指针指向元类对象
我们打印 personClass 的 isa,会发现报错,因为系统没有暴露 isa这个成员:
image.png

为了解决这个问题,我们定义一个和 class相同结构相同的 hh_objc_class:

struct objc_class {
    Class _Nonnull isa;
    Class _Nullable super_class
}

然后把Class personClass类型转换为struct hh_objc_class类型:

struct hh_objc_class *hh_personClass = (__bridge struct hh_objc_class *)(personClass);

然后再打印:
p/x hh_personClass->isa发现结果为:0x001d800100001401
p/x personMetaClass的结果为:0x0000000100001400
同样在按位或运算:


按位或运算后的结果和 p/x personMetaClass的结果: 0x0000000100001400相同,也印证了我们之前的结论.

现在来验证一下类对象的 superClass 指向父类对象也就是说Student classsuperClass地址和Person class的地址相同.
还是之前代码,我们直接打印p/x hh_studentClass->super_classp/x personClass,结果如图:

OC对象的底层结构及isa、superClass详解_第5张图片

通过打印的结果可以看出,superClassisa不同,superClass并没有& ISA_MASK,而是直接相等.

我们一直在说属性信息、协议信息、成员变量信息、对象方法存放在类对象中,类方法存放在元类对象中,但是一直没有亲眼看到,接下来我们就来验证一下,亲眼看看.
首先我们知道,类对象和元类对象的结构是一模一样的,因为他们都是class类型.所以我们点击进入class类型,发现它是这样:

struct objc_class {
    Class _Nonnull isa  OBJC_ISA_AVAILABILITY;

#if !__OBJC2__
    Class _Nullable super_class                              OBJC2_UNAVAILABLE;
    const char * _Nonnull name                               OBJC2_UNAVAILABLE;
    long version                                             OBJC2_UNAVAILABLE;
    long info                                                OBJC2_UNAVAILABLE;
    long instance_size                                       OBJC2_UNAVAILABLE;
    struct objc_ivar_list * _Nullable ivars                  OBJC2_UNAVAILABLE;
    struct objc_method_list * _Nullable * _Nullable methodLists                    OBJC2_UNAVAILABLE;
    struct objc_cache * _Nonnull cache                       OBJC2_UNAVAILABLE;
    struct objc_protocol_list * _Nullable protocols          OBJC2_UNAVAILABLE;
#endif

} OBJC2_UNAVAILABLE;
/* Use `Class` instead of `struct objc_class *` */

可以看到这个结构体中的确是有method_list,ivar_list,protocol_list等等,完全符合我们之前的说法,但是请注意:#if !__OBJC2__,也就是说这段代码的执行有一个前提不是objc2.0才会执行,而现在我们用的都是OC2.0了,所以这段代码是不会执行的.也就是说这段代码已经过时了OBJC2_UNAVAILABLE.所以我们需要从 runtime 的源码中查看.
打开 runtime 源码搜索struct objc_class找到struct objc_class : objc_object {这个结构体,我把主要成员挑出来,如下:

struct objc_class : objc_object {
    // 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_rw_t *data() { 
        return bits.data();
    }
}

我们主要研究class_rw_t,rw_tread_write_table的缩写,意思是可读可写的表.bits.data()返回class_rw_t.class_rw_t里面存放的什么信息呢?我们点击进入class_rw_t看看:

struct class_rw_t {
    // Be warned that Symbolication knows the layout of this structure.
    uint32_t flags;
    uint32_t version;

    const class_ro_t *ro;

    method_array_t methods;//方法信息
    property_array_t properties;//属性信息
    protocol_array_t protocols;//协议信息
}

可以看到我们想看的方法,属性,协议信息.只是还没看到成员变量信息. class_rw_t中有一个class_ro_t,ro_treadOnly_table的缩写,意思是只读的表.我们点击进入const class_ro_t看看里面存放哪些信息:

struct class_ro_t {
    const char * name;//类名
    method_list_t * baseMethodList;
    protocol_list_t * baseProtocols;
    const ivar_list_t * ivars;//成员变量
}

我画了一张图,能一目了然展示Class的内存结构:

OC对象的底层结构及isa、superClass详解_第6张图片
Class类和元类内存结构图

这就是class类型在 runtime 中的源码,到这里我们已经看到了所有我们想到看的信息,但是这只是在源码上证明了结论,最好我们能把代码跑起来,在内存中看看是否是这样,要想做到这种效果,就要借助李哥用 c++ 仿写的一个class类型:MJClassInfo.h文件.
步骤:
1: 导入MJClassInfo.h文件到我们工程中,这是编译会出错,我们把main.m后缀改为main.mm,让编译器编译OC 和 C++ 文件.
2: 创建Student classPerson class然后转换为mj_objc_class类型

mj_objc_class *studentClass = (__bridge mj_objc_class *)[Student class];
mj_objc_class *personClass = (__bridge mj_objc_class *)[Person class];

3: 调用mj_objc_classdata()方法,返回class_rw_t,这是李哥封装的方法,在data()内部调用bits.data()和runtime源码实现一致.

class_rw_t *studentClass_rw_t_Data = studentClass->data();
class_rw_t *personClass_rw_t_Data = personClass->data();

4:打断点,查看class_rw_t内存数据:

OC对象的底层结构及isa、superClass详解_第7张图片

这样我们也从内存上查验了 class的内存结构,meta-class的查验方法和 class一样,我就不写步骤了,直接上图大家撸一眼:
OC对象的底层结构及isa、superClass详解_第8张图片

大总结:

1: 对象的 isa 指针指向哪里?

  • instance 对象的 isa 指针指向 class 对象
  • class 对象的 isa 指针指向 meta-class对象
  • meta-class 对象的 isa 指向基类的 meta-class 对象

2: OC的类信息存放在哪里?

  • 属性信息,协议信息,成员变量信息,对象方法存放在类对象中.
  • 类方法存放在 meta-class 对象中.
  • 成员变量的具体指存放在 instance 对象.

3: 类对象和元类对象什么时候加载?什么时候释放?

  • 当程序执行main函数后就会加载类信息,此时加载到内存;
  • 程序运行过程中一直存在,直到程序退出时销毁.

4: 实例对象中的成员变量和类对象中的成员变量有什么不同?

  • 实例对象中存储的成员变量的具体值
  • 类对象中的存储的成员变量的类型,名字等信息,只存储一份

你可能感兴趣的:(OC对象的底层结构及isa、superClass详解)