一、Class的本质
下列代码是仿照objc_class结构体,提取其中需要使用到的信息,自定义的一个结构体。
#import
#ifndef XXClassInfo_h
#define XXClassInfo_h
# if __arm64__
# define ISA_MASK 0x0000000ffffffff8ULL
# elif __x86_64__
# define ISA_MASK 0x00007ffffffffff8ULL
# endif
#if __LP64__
typedef uint32_t mask_t;
#else
typedef uint16_t mask_t;
#endif
typedef uintptr_t cache_key_t;
struct bucket_t {
cache_key_t _key;
IMP _imp;
};
struct cache_t {
bucket_t *_buckets;
mask_t _mask;
mask_t _occupied;
};
struct entsize_list_tt {
uint32_t entsizeAndFlags;
uint32_t count;
};
struct method_t {
SEL name;
const char *types;
IMP imp;
};
struct method_list_t : entsize_list_tt {
method_t first;
};
struct ivar_t {
int32_t *offset;
const char *name;
const char *type;
uint32_t alignment_raw;
uint32_t size;
};
struct ivar_list_t : entsize_list_tt {
ivar_t first;
};
struct property_t {
const char *name;
const char *attributes;
};
struct property_list_t : entsize_list_tt {
property_t first;
};
struct chained_property_list {
chained_property_list *next;
uint32_t count;
property_t list[0];
};
typedef uintptr_t protocol_ref_t;
struct protocol_list_t {
uintptr_t count;
protocol_ref_t list[0];
};
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;
};
struct class_rw_t {
uint32_t flags;
uint32_t version;
const class_ro_t *ro;
method_list_t * methods; // 方法列表
property_list_t *properties; // 属性列表
const protocol_list_t * protocols; // 协议列表
Class firstSubclass;
Class nextSiblingClass;
char *demangledName;
};
#define FAST_DATA_MASK 0x00007ffffffffff8UL
struct class_data_bits_t {
uintptr_t bits;
public:
class_rw_t *data() {
// 提供data()方法进行 & FAST_DATA_MASK 操作
return (class_rw_t *)(bits & FAST_DATA_MASK);
}
};
/* OC对象 */
struct xx_objc_object {
void *isa;
};
/* 类对象 */
struct xx_objc_class : xx_objc_object {
Class superclass;
cache_t cache;
class_data_bits_t bits;
public:
class_rw_t* data() {
return bits.data();
}
// 提供metaClass函数,获取元类对象
xx_objc_class* metaClass() {
// isa指针需要经过一次 & ISA_MASK操作之后才得到真正的地址
return (xx_objc_class *)((long long)isa & ISA_MASK);
}
};
#endif /* XXClassInfo_h */
根据结构体中的内容及其关系,总结如下图:
可以看出,每个类都对应有一个class_rw_t结构体,class_rw_t结构体内有一个指向class_ro_t结构体的指针。在编译期间,class_ro_t结构体就已经确定,objc_class中的bits的data部分存放着该结构体的地址。在runtime运行之后,具体说来是在运行runtime的realizeClass方法时,会生成class_rw_t结构体,该结构体包含了class_ro_t,并且更新data部分,换成class_rw_t结构体的地址。细看两个结构体的成员变量会发现很多相同的地方,他们都存放着当前类的属性、实例变量、方法、协议等等。区别在于:class_ro_t存放的是编译期间就确定的;而class_rw_t是在runtime时才确定,它会先将class_ro_t的内容拷贝过去,然后再将当前类的分类的这些属性、方法等拷贝到其中。所以可以说class_rw_t是class_ro_t的超集,当然实际访问类的方法、属性等也都是访问的class_rw_t中的内容。
二、isa的本质
OC对象在内存中的排布是一个结构体,其大致框架如下图:
每个对象结构体的首个成员是个Class类型的变量,该变量定义了对象所属的类,通常称为isa指针。在arm64位下的iOS操作系统中,OC对象的isa区域不再只是一个指针,需要经过一次位运算之后才得到真正的地址。用 64 bit 存储一个内存地址显然是种浪费,毕竟很少有那么大内存的设备。于是可以优化存储方案,用一部分额外空间存储其他内容。isa 指针第一位为 1 即表示使用优化的 isa 指针,这里列出不同架构下的 64 位环境中 isa 指针结构:
union isa_t
{
isa_t() { }
isa_t(uintptr_t value) : bits(value) { }
Class cls;
uintptr_t bits;
#if SUPPORT_NONPOINTER_ISA
# if __arm64__
# define ISA_MASK 0x00000001fffffff8ULL
# define ISA_MAGIC_MASK 0x000003fe00000001ULL
# define ISA_MAGIC_VALUE 0x000001a400000001ULL
struct {
uintptr_t indexed : 1;
uintptr_t has_assoc : 1;
uintptr_t has_cxx_dtor : 1;
uintptr_t shiftcls : 30; // MACH_VM_MAX_ADDRESS 0x1a0000000
uintptr_t magic : 9;
uintptr_t weakly_referenced : 1;
uintptr_t deallocating : 1;
uintptr_t has_sidetable_rc : 1;
uintptr_t extra_rc : 19;
# define RC_ONE (1ULL<<45)
# define RC_HALF (1ULL<<18)
};
# elif __x86_64__
# define ISA_MASK 0x00007ffffffffff8ULL
# define ISA_MAGIC_MASK 0x0000000000000001ULL
# define ISA_MAGIC_VALUE 0x0000000000000001ULL
struct {
uintptr_t indexed : 1;
uintptr_t has_assoc : 1;
uintptr_t has_cxx_dtor : 1;
uintptr_t shiftcls : 44; // MACH_VM_MAX_ADDRESS 0x7fffffe00000
uintptr_t weakly_referenced : 1;
uintptr_t deallocating : 1;
uintptr_t has_sidetable_rc : 1;
uintptr_t extra_rc : 14;
# define RC_ONE (1ULL<<50)
# define RC_HALF (1ULL<<13)
};
# else
// Available bits in isa field are architecture-specific.
# error unknown architecture
# endif
// SUPPORT_NONPOINTER_ISA
#endif
};
下面是一些位所代表的的含义
三、对象,类对象,元类对象的关系
上图所示即为对象、类对象、元类对象之间的关系,总结如下:
1.每一个对象中都包含一个isa对象。
2.实例的isa指针指向类,类是一个objc_class结构体,包含实例的方法列表、参数列表、category等,除此之外,objc_class中还有一个super_class,指向其类的父类。
3.类的isa指针指向元类,即metaClass,元类存储类方法等信息。元类里也包含isa指针,元类里的isa指针指向根元类,根元类的isa指针指向自己。
4.obj_msgSend发送实例消息的时候,先找到实例,然后通过实例的isa指针找到类的方法列表及参数列表等,如果找到则返回,如果没有找到,则通过super_class在其父类中重复此过程。
5.obj_msgSend发送类消息的时候,通过类的isa找到元类,然后流程与步骤4相同。
四、消息传递机制
OC是一门非常动态的语言,以至于确定调用哪个方法被推迟到了运行时,而非编译时。与之相反,C语言使用静态绑定,也就是说,在编译期就能决定程序运行时所应该调用的函数,所以在C语言中,如果某个函数没有实现,编译时是不能通过的。而OC是相对动态的语言,运行时还可以向类中动态添加方法,所以编译时并不能确定方法到底有没有对应的实现,编译器在编译期间也就不能报错。
对象的方法调用用OC的术语来讲叫做“给某个对象发送某条消息”。在运行时,编译器会把方法调用转化为一条标准的C语言函数调用,即objc_msgSend(),该函数是运行时消息传递机制中的核心函数。
对象的方法调用步骤如下:
1.实例对象的方法调用要先通过实例的isa指针找到类,随后去该类的方法 cache 中查找,如果找到了就返回它。
2.如果没有找到,就去该类的方法列表中查找。如果在该类的方法列表中找到了,则将 IMP 返回,并将它加入cache中缓存起来。根据最近使用原则,这个方法再次调用的可能性很大,缓存起来可以节省下次调用再次查找的开销。
3.如果在该类的方法列表中没找到对应的 IMP,再通过该类结构中的 super_class指针在其父类的方法 cache和方法列表中查找。当在某个父类的方法 cache或方法列表中找到对应的 IMP,就返回它,否则就继续循环,直到基类。
4.如果在自身以及所有父类的方法 cache和方法列表中都没有找到对应的 IMP,则进入消息转发流程。
5.类对象的方法调用要通过类的isa找到元类,随后到元类及其所有父类的方法 cache 和方法列表中进行查找,流程与步骤1~4相同。
五、消息转发机制
消息传递过程中会在相关的类对象中搜索方法列表,如果找不到则会沿着继承树向上一直搜索直到继承树根部(通常为NSObject),如果还是找不到就进行消息转发,如果消息转发失败了就会执行doesNotRecognizeSelector:方法报unrecognized selector错。消息转发主要分三步:动态方法决议、备用接收者、完整消息转发,流程如下: