学习MJ的视频课程,整理并记录知识点–isa和superclass

[TOC]

结合之前的文章,我们已经知道objc中的对象是如下的内存结构如下:
OC对象的分类

这里我们探究isa指针和superclass指针的作用。

对象的isa指针

提出问题:对象的isa指针指向哪里?

分析:

  1. 我们知道实例对象只保存成员变量信息,它对应的对象方法信息是保存在实例对应的类(class)对象中(因为同一个类的生成的实例默认的对象方法肯定一样,因为系统不会不优雅的为每个实例对象都开辟空间保存同样的方法信息)。
  2. 那么我们用实例对象调方法时,肯定要知道这个方法的信息,这些信息存储位置必然和类有关,所以实例对象必然能找到它对应的类,我们结合OC对象的本质一节,已经得知,一个没有任何变量的实例对象都有isa指针,做排除法也可得知,实例对象的isa指针指向它的类(class)对象。
  3. 同理,类(class)对象的方法信息是存储在它的元类(meta-class)中;类(class)对象的isa指针必然指向它的元类(meta-class)对象。

这三种对象之间是通过isa指针联系起来的
isa指针

  • instance的isa指向class,当调用对象方法时,通过instance的isa找到class,最后找到对象方法的实现进行调用
  • class的isa指向meta-class,当调用类方法时,通过class的isa找到meta-class,最后找到类方法的实现进行调用

class的superclass指针

superclass我们知道它是指向父类对象(super class),这里不再啰嗦,可以看下图
class的superclass指针

meta-class的superclass指针

meta-class 主要存放的是class的类方法,meta-class之间也是有对应的superclass指针的,这里不再啰嗦,可以看下图
meta-class的superclass指针

isa、superclass总结

附上一张比较比较有名的来总结isa、superclass指针,如下
isa、superclass总结

有两个地方需要注意:

  1. meta-class的isa指向基类的meta-class
  2. 基类的meta-class的superclass指针,指向基类的class

总结:isa和superclass是重要的两根线,来联通 instance、class、meta-class

关于方法的调用顺序我们知道,无论是实例对象还是类对象,都是先在当前类找看是否有和这个方法的实现,没有的话回去父类找,直到找到或者抛出异常。

下面我们举class方法的调用例子,来加深理解,源码在这里下载
主要代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
@interface MJPerson : NSObject
+ (void)test;
@end

@implementation MJPerson
@end

@interface NSObject (Text)
@end

@implementation NSObject (Test)

//+ (void)test {
// NSLog(@"+[NSObject test] - %p", self);
//}

- (void)test {
NSLog(@"-[NSObject test] - %p", self);
}

@end


int main(int argc, const char * argv[]) {
@autoreleasepool {
// 调用一个没有实现的类方法,结果会怎样?
[MJPerson test];
}
return 0;
}

演示中,我们使用MJPerson调用类方法test,但是test方法是没有实现的,只有基类实现了一个对象方法。调用结果会崩溃吗?答案是程序不会崩溃,类可以调用到对象方法,看起来似乎有点让人震惊。

我们分析一下调用流程来解释为什么会是这样:
首先是类对象调用方法,类对象的方法信息放在元类里面,MJPerson会去自己的元类对象找,没有找到(因为已经被注释了)。继续去父类的元类对象找(NSObject的元类),也没找到。但是,因为NSObject的元类还有superclass,就是NSObject,所以会继续去NSObject中找test方法的实现,找到了,然后调用这个方法,调用流程结束。
流程如下:
类调用对象方法
Demo下载

meta-class底层机构和class是一致,都是class,不要被方法的符号迷惑(+、-),objc消息机制只要求对象、方法名,实例方法、类方法没有本质却别。只会依据调用规则通过isa和superclass指针来查找,流程简述如下isa -> superclass -> suerpclass -> superclass -> .... superclass == nil

Java不一样,OC不是严格的面向对象,是对C语言的扩展,增加消息机制。

isa指针窥探

前面我们知道instance对象的isa指向类对象,这里通过查看内存进行验证。

我们通过断点debug能找到一个对象的变量信息,但是isa变量的信息我们却看不到,此时,我们可以利用前面学习的LLDB调试指令,打印内存地址p/x (long)instance->isa,当这个打印结果和class对象的地址一直,即可验证instance对象的isa指向类对象。

但实际上的打印结果发现却发现两者不一致,因为苹果规定从64bit开始,isa需要和ISA_MASK进行一次与运算,才能计算出真实地址

那么class对象的isa是不是也需要进行一次ISA_MASK的与运算,我们来验证一下。

通过p/x (long)class对象->isa报错,提示member reference base type 'Class' is not a structure or union,我们只能先把class转成struct。

根据class的结构类型,定义MJPerson对应的结构体,如下:

1
2
3
4
struct mj_objc_class {
Class isa;
Class superclass;
};

在使用时,直接将class桥接转成对应的结构体

1
struct mj_objc_class *personClass = (__bridge struct mj_objc_class *)([MJPerson class]);

此时我们就能打印结构体的isa指针了。我们打印指针地址,与上ISA_MASK后得到的地址确实为MJPerson的meta-class地址。总结如下图:
ISA_MASK

class和meta-class的结构

本小节窥探class、meta-class内存的结构,来证明我们之前推测的class的struct结构模型。

想了解class的结构细节,我们还是需要去看objc的源码,在objc-runtime-new.h文件中,我们能看到objc_class大致结构如下:
objc_class

思路:我们可以自己创建一个class,然后仿照objc_class的定义,构建类似的结构体,然后把class桥接转换成结构体,如果结构体内有对应的值,说明假设成立。

这里我就不粘贴代码了,这里已经通过真实的Demo进行测试,可以下载进行演示和验证Demo下载

源码和参考

Demo源码:

评论