一.首先我们来看一下怎么获取对象的实例对象,类对象,元类对象
实例对象通过alloc,new获取,例:
NSObject *instanceObjc = [[NSObject alloc] init];
类对象通过class方法或者object_getClass(需要导入#import <objc/runtime.h>)获取,例:
Class classObjc = object_getClass(instanceObjc);
Class classObjc2 = [instanceObjc class];
元类对象通过object_getClass获取,例:
Class metalClass = object_getClass(classObjc);
二.解析实例对象,类对象,元类对象的数据结构
实例对象解析:
我们创建一个Person类,新增成员变量_no和属性name
@interface Person : NSObject { int _no; } @property (nonatomic, copy) NSString *name; @end通过c++代码转换后,我们可以看到Person的数据结构为
struct Person_IMPL { struct NSObject_IMPL NSObject_IVARS; int _no; NSString *_name; }; struct NSObject_IMPL { Class isa; };通过源码解析我们可以看到实例对象包含了一个Class指针isa以及成员变量及属性,isa指针我们放在后面统一说,这里先简单认为可以通过isa指针找到类对象
类对象及元类对象解析:
我们可以发现类对象及元类对象的类型是一样的,都是Class类型,接下来我们通过runtime源码去解析一下
在objc文件里可以看到Class类型其实是struct objc_class
typedef struct objc_class *Class; struct objc_object { Class _Nonnull isa OBJC_ISA_AVAILABILITY; };接下来我们全局搜索struct objc_class,发现有多个文件包含,我们查看objc-runtime-new,这个文件里面是最新的,我们发现类对象和元类对象的结构体如下(这里只展示我们需要的信息,想查看完整信息可以去查看源码)
struct objc_object { isa_t isa; } struct objc_class : objc_object { Class superclass; cache_t cache; class_data_bits_t bits; class_rw_t *data() { return bits.data(); } }通过这一段数据结构可以看到类对象及元类对象包含了一个isa指针,一个superclass指针,一个cache,一个bits,接下来我们来一一解析
在这里给大家放一张网上流传比较广的图,便于我们更好的认识isa指针和superclass指针
图片中虚线就是isa指针,实线是superclass指针
isa指针解析:
可以很清晰的看到,实例对象的isa指针指向类对象,类对象的指针指向元类对象,元类对象的isa指针指向基类的元类对象(注意这里在64位之前isa是直接存储对应的地址值,例如实例对象的isa直接存储类对象的地址值,但是在64位之后,需要isa&ISA_MSAK才能获取类对象的地址值)
superclass指针解析:
实例对象没有superclass指针,类对象的superclass指针指向类对象的父类一直到基类,元类对象的superclass指针指向元类对象的父类直到基类,元类对象的基类对象的superclass指针指向类对象的基类对象(也就是图中的RootClass(meta)指向RootClass(class))
cache解析:
查看runtime源码中的struct cache_t
struct cache_t { struct bucket_t *_buckets;//散列表 mask_t _mask;//散列表的长度-1 mask_t _occupied;//已经缓存的方法数量 }可以看到这个里面主要是存储了方法的缓存,缓存列表_buckets是一个散列表或者叫哈希表,假如我们之前调用过这个方法的话,就会直接存在缓存里,下次直接从缓存取就可以,大大加快了方法调用的速度
bits解析:
class_rw_t *data() { return bits.data(); } class_rw_t* data() { return (class_rw_t *)(bits & FAST_DATA_MASK); }可以看到bits通过&FAST_DATA_MASK可以得到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; }
可以看到包含了一个class_ro_t的数据结构,方法信息(类对象是实例方法,元类对象是类方法),属性信息,协议信息,第一个子类,下一个兄弟类,猜测class_rw_t是设计一种二叉树的结构待验证,接下来继续查看class_ro_t
**struct** class_ro_t { uint32_t flags; uint32_t instanceStart; uint32_t instanceSize; 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; };可以看到这里存储的是成员变量信息列表(实例对象存储的是成员变量具体的值,这里存储的是成员变量的描述信息),类名,base方法列表,base协议列表,base属性列表。
总结:实例对象存储了isa指针及成员变量的值,类对象存储了实例方法,成员变量信息,协议信息,属性信息等,元类对象存储了类方法列表。
三:通过分析方法调用来加深一下上面的印象
假如我们给Person类添加了一个实例方法和类方法
@interface Person : NSObject
- (void)instanceMethodTest;
+ (void)classMethodTest;
@end
执行实例方法
Person *p = [[Person alloc] init]; [p instanceMethodTest];这里会先拿到实例对象p的isa指针,通过&ISA_MSAK找到Person的类对象(因为实例方法存储在类对象里),看看有没有实现,如果实现则存入cache,下次直接从缓存中获取,如果没有实现,会通过superClass指针去父类中找,如果父类实现则直接调用,如果没有实现则继续往上找(这也就是为什么子类可以调用父类的方法),直到基类,如果基类还没有实现,则会走消息转发机制,如果没有处理,则抛出错误unrecognized selector sent to instance****
执行类方法
[Person classMethodTest];这里会先拿到类对象Person的isa指针,通过&ISA_MASK找到Person的元类对象(因为类方法存储在元类对象里),看看有没有实现,如果实现则存入cache,下次直接从缓存中获取,如果没有实现,会通过superClass指针去父类中找,如果父类实现则直接调用,如果没有实现则继续往上找,直到基类,如果基类也没有实现,会去Person基类的类对象中去查找,如果还没有找到,则会走消息转发机制,如果没有处理,则抛出错误unrecognized selector sent to instance****