isa 指针
OC 的实例对象、类对象以及元类对象,内部都有一个 isa 指针,那么这些类型的 isa 指针有什么作用?或者说分别指向哪里?
我们知道 OC 方法调用在编译时都会转化为 C 函数 objc_msgSend 的调用。
比如 Person 类,有一个实例方法 instanceMethod 和一个类方法 classMethod
Person *p = [[Person alloc] init];
// 对象方法调用
[p instanceMethod];
// 类方法调用
[Person classMethod];
objc_msgSend(p, @selector(instanceMethod));其中调用的实例方法 instanceMethod 存储在类对象中,给 p 对象发送消息时,如何找到实例方法的?同样给类对象 Person 发送的类方法 classMethod 是存储在元类对象相中,消息发送时又是如何找到元类对象查找方法的?
这其实是通过 isa 完成的,即:
-
instance 的 isa 指向 class:
当调用对象方法时,通过 instance 的 isa 找到 class,最后找到对象方法的实现进行调用。 -
class 的 isa 指向 meta-class:
当调用类方法时,通过 class 的 isa 找到 meta-class,最后找到类方法的实现进行调用。
superclass
类对象的 superclass
假设 Male类 继承 Person类,Person类 继承 NSObject。那么:
1)Male类 信息中的 superclass指针 指向 Person类;
2)Person类 信息中的 superclass指针 指向 NSObject,
类对象的 superclass指针 指向父类的类对象;
superclass 在实例对象方法调用过程:
当子类的的 instance 对象想要调用父类的的对象方法时,会先通过 isa 找到子类的 class,然后通过 superclass 找到父类的 class,最后找到对象方法的实现进行调用。
元类对象的 superclass
元类对象的 superclass指针 指向父类的元类对象;
superclass 在类方法调用过程:
当子类的 class 要调用父类的类方法时,会先通过 isa 找到子类的m eta-class,然后通过 superclass 找到父类的 meta-class,最后找到类方法的实现进行调用。
isa 和 superclass 总结
引用经典的一张图:
-
instance 的 isa 指向 class
-
class 的 isa 指向 meta-class
-
meta-class 的 isa 指向基类的 meta-class
Subclass(meta) 和 Superclass(meta) 的 isa 都指向 Rootclass(meta),同样 Rootclass(meta) 的 isa 指向 Rootclass(meta)。 -
class 的 superclass 指向父类的 class
如果没有父类,superclass指针为nil -
meta-class的superclass指向父类的meta-class
基类的 meta-class 的 superclass 指向基类的 class。即图中的 Rootclass 的元类对象的 superclass 指向的是 Rootclass 的类对象 -
instance 调用对象方法的轨迹
isa 找到 class,方法不存在,就通过 superclass 找父类...一直到 superclass 为 nil 为止; -
class 调用类方法的轨迹
isa 找 meta-class,方法不存在,就通过superclass找父类...一直到 superclass 为 nil 为止;
注意:由于基类的 meta-class 的 superclass 指向基类的 class。所以类方法调用的时候,当沿着 superclass 找到基类元类对象《Rootclass(meta)》,如果基类的元类对象中无该类方法,会继续沿着 superclass 找到基类的类对象《Rootclass(class)》,在基类的类对象中查找对象方法,找到则会调用对象方法。
ps:上面两个方法调用轨迹,只是为了说明 isa 和 superclass 在方法调用中的应用场景,只是大致的调用轨迹。
针对上面 class 调用类方法的轨迹 的注意事项,代码测试:
为 NSObject 添加分类,并添加类方法 test 的申明,不做类方法的实现,实现同名对象方法:
NSObject+Category.h 中
@interface NSObject (Category)
+ (void)test;
@end
NSObject+Category.m 中
@implementation NSObject (Category)
- (void)test
{
NSLog(@"[NSObject test] - 实例方法");
}
@end
Person 类继承 NSObject,只申明类方法 test 而不实现
Person.h 中
@interface Person : NSObject
+ (void)test;
@end
Person.m
@implementation Person
@end
测试类方法调用:
- (void)viewDidLoad {
[super viewDidLoad];
[Person test];
}
打印日志:
Isa_Test01[33875:3708779] [NSObject test] - 实例方法
可以看到,调用 Person 的类方法(+ (void)test),最终来到 NSObject 的 对象方法实现中。
进一步探讨 isa
前面我们总结到:instance 的 isa 指向 class;class 的 isa 指向 meta-class。那么 isa 指针值等于类对象或者元类对象的地址么?
通过代码:
- (void)viewDidLoad {
[super viewDidLoad];
Person *person = [[Person alloc] init];
Class personClass = [Person class];
Class personMetaClass = object_getClass(personClass);
NSLog(@"实例对象地址: %p", person);
NSLog(@"类对象地址: %p", personClass);
NSLog(@"元类对象地址: %p", personMetaClass);
}
断点调试,分别打印实例对象 person 的 isa 的地址值,和类对象 personClass 的地址值
可以看到真机上,person->isa 的地址 0x000001a102dad579 不等于 Person 类对象的地址 0x0000000102dad578。
person->isa 与(&)ISA_MASK之后的值才为 Person 类对象的地址值。
在 objc 源码中对于 ISA_MASK 的定义:
所以在 iPhone 上(arm64架构),ISA_MASK 取值 0x0000000ffffffff8ULL,继续调试,将 person->isa的地址 & 0x0000000ffffffff8ULL,结果为0x0000000102dad578,和 Person类对象 地址相同。
按照统一的操作打印类对象的 isa,控制台报错:
此时可以自定义 struct cw_objc_class,同源码的 struct objc_class 结构体类型相似的结构体类型:
struct cw_objc_class {
Class _Nonnull isa OBJC_ISA_AVAILABILITY;
};
并将类对象(personClass)强制转换成自定义的 struct cw_objc_class
- (void)viewDidLoad {
[super viewDidLoad];
Person *person = [[Person alloc] init];
Class personClass = [Person class];
struct cw_objc_class *cw_personClass = (__bridge struct cw_objc_class *)(personClass);
Class personMetaClass = object_getClass(personClass);
}
再次断点调试:
可以看到类对象 personClass 的 isa 与(&) ISA_MASK 即为 元类对象 personMetaClass 的地址值。
所以准确的说:对象或者类对象的 isa 指针间接指向 类对象或者元类对象,即需要将 isa & ISA_MASK 才是类对象或者元类对象的地址。
而对于 superclass 可以验证,类对象或者元类对象的 superclass 直接 指向父类类对象或者父类元类对象。
Class 的结构
类对象、元类对象在内存中的结构是一样的,接下来从底层源码了解类对象或者元类对象的信息。
类对象、元类对象的类型都是 Class:
typedef struct objc_class *Class;
因此只需要去了解 struct objc_class,但直接从 xcode 看到struct objc_class 的定义中注释:OBJC2 已不可用
所以想要了解 struct objc_class 需要去 objc 源码中查看:
而其继承的 objc_object 中有个成员变量 isa:
因此 struct objc_class 的结构体内存可以大致理解为:
注意:只是说可以这样理解。
struct objc_class {
Class isa;
Class superclass;
cache_t cache; // 方法缓存
class_data_bits_t bits; // 用于获取具体的类信息
}
其中获取具体的类信息 class_data_bits_t bits,是通过调用 data() 方法获取:
并且 bits & FAST_DATA_MASK 得到 struct class_rw_t:
struct 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 firstSubclass;
Class nextSiblingClass;
char *demangledName;
}
其中 const class_ro_t *ro 的信息为:
struct class_ro_t {
uint32_t flags;
uint32_t instanceStart;
uint32_t instanceSize; // instance 对象所占用的内存空间
#ifdef __LP64__
uint32_t reserved;
#endif
const uint8_t * ivarLayout;
const char * name; // 类名
method_list_t * baseMethodList;
protocol_list_t * baseProtocols;
const ivar_list_t * ivars; // 成员变量列表
const uint8_t * weakIvarLayout;
property_list_t *baseProperties;
method_list_t *baseMethods() const {
return baseMethodList;
}
};
总结:
1、类对象class、元类对象meta-class 的本质结构都是 struct objc_class:
2、struct objc_class 在内存中的结构为:
_