思考:
- 对象的 isa 指针指向哪里?
- OC 的类信息存放在哪里?
图解
- instance 的 isa 指向 class
- class 的 isa 指向 meta-class
- meta-class 的 isa 指向基类的 meta-class
- class 的 superclass 指向父类的 class,如果没有父类,superclass 指针为nil
- meta-class 的 superclass 指向父类的 meta-class,基类的 meta-class 的 superclass 指向基类的 class
- instance 调用对象方法的轨迹:isa 找到 class,方法不存在,就通过 superclass 找父类
- class 调用类方法的轨迹:isa 找 meta-class,方法不存在,就通过 superclass 找父类
isa
instance 对象、class 对象 和 meta-class 对象之间的 isa 关系
定义 Person
|
|
创建 Person 的实例对象
将 OC 代码转换为 C\C++ 代码
找到 main.m 所在文件,在终端输入:
没有通过 ‘-o’ 生成指定文件时,默认生成 main.cpp 文件,打开 main.cpp 文件。找到 main 函数:
找到 objc_msgSend:
[person personInstanceMethod]
的具体实现是 objc_msgSend(person, sel_registerName("personInstanceMethod"))
。
即在实例对象 person 调用 -(void)personInstanceMethod
对象方法的时候,向实例对象 person 发送一条 “personInstanceMethod” 消息。
[Person personClassMethod]
的具体实现是 objc_msgSend(objc_getClass("Person"), sel_registerName("personClassMethod"))
。
即在类对象 Person 调用 +(void)personClassMethod
类方法的时候,向类对象 Person 发送一条 “personClassMethod” 消息。
方法调用与对象的关系
|
|
上面👆两个方法调用表现出来的是,实例对象 person 可以调用存在 Person 类对象里的对象方法, Person 类对象可以调用存储在 Person 元类对象里的类方法。
小结
- instance 对象的 isa 指针指向 class 对象。当调用对象方法时,通过 instance 对象的 isa 指针找到 class 对象,最后找到对象方法的实现进行调用。
- class 对象的 isa 指针指向 meta-class 对象。当调用类方法时,通过 class 对象的 isa 指针找到 meta-class对象,最后找到类方法的实现进行调用。
ISA_MASK
|
|
实例对象的 isa 指针
打印 Person->isa、personClass:
上面👆的打印结果可以看到,Person 的类对象地址是 0x00000001000014f0,而 Person 的实例对象的 isa 指针的地址是 0x001d8001000014f1。
不相等原因是在 64bit 之前 isa 指针的地址等于被指向对象的地址,从 64bit 开始 isa 需要进行一次位运算,才能计算出真实地址:
ISA_MASK 在源码 objc4-781 中的定义:
iPhoneOS 是 arm64 架构,ISA_MASK:0x0000000ffffffff8。
MacOS 是 x86_64 架构,ISA_MASK:0x00007ffffffffff8ULL。
打印 person->isa & ISA_MASK
上面👆的打印结果可以看出,Person 实例对象的 isa 指针 & ISA_MASK 就是 Person 类对象的地址。
类对象的 isa 指针
类对象的类型 Class 是一个指向结构体 objc_class 的指针:
Jump TO Definition -> objc_class:
👆 objc_class 的 isa 是不支持外部访问的,所以 personClass->isa 获取不到 isa 指针地址,所以要自定义一个 test_objc_class 结构体,再将 personClass 的类型强转为 test_objc_class 类型:
因为 personClass 是 OC 对象,将 OC 代码专为 c++ 代码需要用到桥接 (__bridge struct test_objc_class *)。
打印 personClass2->isa、personMetaClass 和 personClass2->isa & ISA_MASK:
上面👆的打印结果可以看出,Person 类对象的 isa 指针 & ISA_MASK 就是 Person 元类对象的地址。
superclass
定义 Student 继承自 Person:
创建 Student 的实例对象
class 对象的 superclass 指针
Student 类对象、Person 类对象 和 NSObject 类对象之间的 superclass 关系:
获取 test_objc_class 类型的 Person 类对象和 Student 类对象:
打印 personClass、studentClass 和 studentClass->superclass:
上面👆的打印结果可以看出,Student 类对象的 superclass 指针地址就是 Person 类对象的地址。
Student 的实例对象调用父类 Person 里的对象方法:
|
|
对象方法 -(void)personInstanceMethod
保存在 Person 的类对象里,[student personInstanceMethod]
首先通过 student 的 isa 指针找到 Student 的类对象,再通过 Student 类对象里的 superclass 找到 Person 的类对象,最后在 Person 类对象里找到了对象方法 -(void)personInstanceMethod
。
Student 的实例对象调用父类 NSObject 里的对象方法:
|
|
对象方法 -(void)init
方法保存在 NSObject 的类对象里,[student init]
首先通过 student 的 isa 指针找到 Student 的类对象,再通过 Student 类对象里的 superclass 找到 Person 的类对象,再通过 Person 类对象里的 superclass 找到 NSObject 的类对象,最后在 NSObject 类对象里找到了对象方法 -(void)init
。
小结
- 具有继承关系的不同的类之间,是通过 superlass 指针连接的。有了 superlass 指针的连接,就实现了子类调用父类方法的逻辑。
- 当 Student 的 instance 对象在调用 Person 的对象方法时,会先通过 isa 找到 Student 的 class,然后通过 superclass 找到 Person 的 class,最后找到对象方法的实现进行调用。
meta-class 对象的 superclass 指针
Student 元类对象、Person 元类对象 和 NSObject 元类对象之间的 superclass 关系:
Student 类对象调用 Student 元类对象里的类方法:
|
|
首先通过 Student 类对象里的 isa 指针找到 Student 元类对象,最终在 Student 元类对象里找到类方法 +(void)studentClassMethod
。
Student 类对象调用父类 Person 元类对象里的类方法:
|
|
首先通过 Student 类对象里的 isa 指针找到 Student 元类对象,再通过 Student 元类对象里的 superclass 找到 Person 元类对象,最终在 Person 元类对象里找到类方法 +(void)personClassMethod
。
Student 类对象调用父类 NSObject 元类对象里的类方法:
|
|
首先通过 Student 类对象里的 isa 指针找到 Student 元类对象,再通过 Student 元类对象里的 superclass 找到 Person 元类对象,再通过 Person 元类对象里的 superclass 找到 NSObject 元类对象,最终在 NSObject 元类对象里找到类方法 +(void)load
。
instance 对象调用对象方法流程
|
|
向实例对象 student 发送一条 “unrecoginzedSelector” 消息。student 通过 isa 指针找到 Student 类对象,在类对象里查找对象方法 -(void)unrecoginzedSelector
。如果没有,Student 类对象会通过 superclass 指针找到 Student 父类的类对象,并在父类的类对象里查找对象方法 -(void)unrecoginzedSelector
。如果还是没有找到,再通过 superclass 查找父类的类对象。以此往复,直找到基类 NSObject 的类对象。流程图:
unrecoginzed selector sent to instance
如果在 NSObject 的类对象里也没有查找到对象方法 -(void)unrecoginzedSelector
,就会返回出现‘unrecoginzed selector sent to instance’错误。
子类重写父类的对象方法
|
|
向实例对象 student 发送一条 “test” 消息。studnet 通过 isa 指针找到 Student 类对象,在类对象里查找对象方法 -(void)test,找到后返回,不在查找父类的类对象。
class 对象调用类方法流程
|
|
向类对象 Student 发送一条 “unrecoginzedSelector” 消息。Student 通过 isa 指针找到 Student 元类对象,在元类对象里查找类方法 -(void)unrecoginzedSelector
。如果没有,Student 元类对象会通过 superclass 指针找到 Student 父类的元类对象,并在父类的元类对象里查找类方法 -(void)unrecoginzedSelector
。如果还是没有找到,再通过 superclass 查找父类的元类对象。以此往复,直找到基类 NSObject 的元类对象。如果在 NSObject 的元类对象里也没有查找到类方法 -(void)unrecoginzedSelector
,就会通过 superclass 指针找到 NSObject 的类对象查找类方法 -(void)unrecoginzedSelector
。流程图:
unrecoginzed selector sent to class
如果在 NSObject 的类对象里也没找到类方法 -(void)unrecoginzedSelector
,就会返回出现‘unrecoginzed selector sent to class’错误。
子类重写父类的类方法
|
|
向类对象 Student 发送一条 “test” Student 通过 isa 指针找到 Student 元类对象,在元类对象里查找类方法 +(void)test,找到后返回,不在查找父类的元类对象。
class 对象调用对象方法流程
定义 NSObject+test
|
|
打印结果:
[Person test]
向类对象 Person 发送一条 “test” 消息。Person 通过 isa 指针找到 Person 元类对象,在元类对象里查找类方法 +(void)test
。如果没有,Person 元类对象会通过 superclass 指针找到 NSObject 的元类对象,并在 NSObject 的元类对象里查找类方法 +(void)test
。如果还是没有找到,再通过 superclass 指针找到 NSObject 的类对象,在类对象中找到对象方法 -(void)test
并返回。
NSObject+test 里打印的 self,是 objc_msgSend() 里的对象,即接收‘test’消息的对象。[Person test] 中,因为是向 Person 发送了一条”test“消息,所以打印的 self 是 Person 的类对象。流程图:
+ (void)test 与 - (void)test 同时存在
|
|
此时 [Person test] 调用的就是类方法 +(void)test
了。因为 +(void)test
存储在 NSObject 元类对象里,而 -(void)test
存储在 NSObject 类对象里。查找类方法 +(void)test
时,会优先找到 NSObject 元类对象,在元类对象里找到类方法 +(void)test
后返回,不再到类对象里找了。
[NSObject test]
向类对象 NSObject 发送一条 “test” 消息。NSObject 通过 isa 指针找到 NSObject 元类对象,在元类对象里查找类方法 +(void)test 。如果没有,NSObject 元类对象再通过 superclass 指针找到 NSObject 的类对象,在类对象中找到对象方法 -(void)test 并返回。流程图:
class 对象调用对象方法的可能性
|
|
上面👆这句代码的本质是:
向类对象 Person 发送一条 “test” 消息,这条消息里并没有包含方法的类型,即不区分类方法和对象方法。
窥探 struct objc_class 的结构
废弃的 struct objc_class
|
|
在上面👆类对象的 isa 处提到过,类对象和元类对象的类型 Class 是一个指向结构体 objc_class 的指针:
Jump TO Definition -> objc_class:
代码中包含以下代码:
说明,结构体 struct objc_class 在 OBJC2 里被废弃掉了。
新版 struct objc_class
可以在 objc4-781 找到最新的源码,打开 objc-runtime-new.h :
objc_class:
objc_object:
class_rw_ext_t、class_rw_t:
class_rw_t 可以翻译为 class_readWrite_table,即读写表。在 objc4 的旧版本里,class_rw_ext_t 里的成员变量是直接定义在 class_rw_t 里的。
class_ro_t:
class_ro_t 可以翻译为 class_readOnly_table,即只读表。
class_data_bits_t:
class_data_bits_t 内部通过 bits & FAST_DATA_MASK 找到 class_rw_t。
objc_class、class_rw_t 和 class_ro_t 之间的关系可以简化为:
查看 objc_class 对象的真实结构
导入 ClassInfo.h,定义 Person 和 Student 类:
加断点后,可以在控制栏里看到每个类内部的具体信息了。
studentClassData:
可以看到 Student 的类对象里存储了属性、对象方法、协议、成员变量信息。
studentMetaClassData:
可以看到 Student 的元类对象里存储了类方法、协议。属性、成员变量信息都为 NULL。
元类对象中存储的协议信息与类对象中存储的协议信息地址相同,所以是同一份。如何确定协议信息是存储在类对象中还是元类对象中呢?还是两个都存储了?
总结
对象的 isa 指针指向哪里?
instance 对象的 isa 指向 class 对象
class 对象的 isa 指向 meta-class 对象
meta-class 对象的 isa 指向基类的 meta-class 对象OC 的类信息存放在哪里?
对象方法、属性、成员变量、协议信息,存放在 class 对象中
类方法,存放在 meta-class 对象中
成员变量的具体值,存放在 instance 对象
ps:
ClassInfo.h