前言
在iOS开发中,对象、类的使用可以说是无处不在,伴随着整个项目的开发周期,也是程序的重要组成部分。但是在日常开发中,很难直观的见到元类、isa指针,那么它们究竟是谁,在开发中起着什么作用呢?下面我们就分别介绍下这几位亲兄弟的结构体定义,以及之间的关联关系。
类(class)的结构体定义
类对象(Class)是由程序员定义并在运行时有编译器创建的,他没有自己的实例变量,这里需要注意的是类的成员变量和实例方法列表是属于实例对象的,但其存储于类对象当中。下图指出类的结构体定义:
// An opaque type that represents an Objective-C class.
typedef struct objc_class *Class;
// OC1.0 class 结构体定义
struct objc_class {
Class _Nonnull isa OBJC_ISA_AVAILABILITY;
#if !__OBJC2__
Class _Nullable super_class OBJC2_UNAVAILABLE;
const char * _Nonnull name OBJC2_UNAVAILABLE;
long version OBJC2_UNAVAILABLE;
long info OBJC2_UNAVAILABLE;
long instance_size OBJC2_UNAVAILABLE;
struct objc_ivar_list * _Nullable ivars OBJC2_UNAVAILABLE;
struct objc_method_list * _Nullable * _Nullable methodLists OBJC2_UNAVAILABLE;
struct objc_cache * _Nonnull cache OBJC2_UNAVAILABLE;
struct objc_protocol_list * _Nullable protocols OBJC2_UNAVAILABLE;
#endif
} OBJC2_UNAVAILABLE;
/* Use `Class` instead of `struct objc_class *` */
// OC2.0 class 结构体定义,我们注意到上面的定义后面的标识符OBJC2_UNAVAILABLE,意思是已经不适用ObjC2.0了,现在ObjC2.0 对objc_class的定义如下:
struct objc_class : objc_object {
isa_t isa;
Class superclass;
cache_t cache;
class_data_bits_t bits;
};
我们在objc/objc.h
源码文件中找到了Class 类结构体的定义,下面我们分析并说明一下几个关键的结构体成员:
- isa: 是一个不能为空(null)的Class对象,它指向该类的元类(meta class)。
- super_class:是一个可以为空的Class对象,它指向当前类的父类。会一直追溯到根类(NSObject),那么根类(NSObject)的父类是指向谁的呢?通过分析根类的父类是指向空(nil)的,这样也就可以解释为什么
super_class
是可以为空的。 - ivars:是实例变量列表,保存着该类的所有实例变量
- methodLists:是方法列表,保存着实例方法和类方法
- protocols:是协议列表,保存该类的协议方法
- cache:是一个缓存,用于缓存最近使用过的方法。
- …
上面我们根据Class 类的结构体,分析内部成员的作用及含义。想必大家已经对Class类的内部结构有了一个初步的认识。如果想再深入理解isa
和super_class
在不同情况下都是如何进行指向的,我们再看下面的一个图示来逐步分析:
首先,注意图中的注释,实线的箭头(→ superclass)代表父类指针,虚线的箭头(→isa)代表isa
指针,从上至下的深色方框依次代表:根类-> 父类-> 子类,下面咱们从上至下层层分析上图的含义:
- 第一层:
- isa指针: 根类的实例对象(
instance Of Root class)
的isa
指向根类(Root class
),根类的isa
指针指向根元类(Root meta class
),根元类的isa
指向它本身。 - superclass指针:根类(
Root class
)的superclass
指向nil
,根元类的父类指向根类。
- isa指针: 根类的实例对象(
- 第二层:
- isa指针:父类的实例对象(
instance of superclass
)的isa
指向父类(superclass),父类的isa
指向父元类,父元类的isa
指向根元类。 - superclass指针:父类的
superclass
指向根类,父元类的superclass
指向根元类。
- isa指针:父类的实例对象(
- 第三层:
- isa指针:子类实例对象(instance of subclass)的
isa
指向子类,子类的isa
指向子元类,子元类的isa
指向根元类。 - superclass指针:子类的
superclass
指向父类,子元类的superclass
指向父元类。
- isa指针:子类实例对象(instance of subclass)的
动态创建一个类
// 创建一个新类和元类
Class objc_allocateClassPair ( Class superclass, const char *name, size_t extraBytes ); //如果创建的是root class,则superclass为Nil。extraBytes通常为0
// 销毁一个类及其相关联的类
void objc_disposeClassPair ( Class cls ); //在运行中还存在或存在子类实例,就不能够调用这个。
// 在应用中注册由objc_allocateClassPair创建的类
void objc_registerClassPair ( Class cls ); //创建了新类后,然后使用class_addMethod,class_addIvar函数为新类添加方法,实例变量和属性后再调用这个来注册类,再之后就能够用了。
对象(object)的结构体定义
// Represents an instance of a class.
struct objc_object {
Class _Nonnull isa OBJC_ISA_AVAILABILITY;
};
我们可以看到,objc_object
代表一个实例对象,它的结构体内部有一个不为空的isa
指针,isa
是指向对象所属类的指针。另外,我们常用的id
类型是一个指向objc_object
结构体的指针,它可以指向任意一个oc对象。
// A pointer to an instance of a class.
typedef struct objc_object {
Class isa;
} *id;
元类(Meta class)的结构体定义
元类(Meta class) 就是类对象的类,每个类都有自己的元类,也就是objc_class
结构体里面的isa
指针所指向的类。元类用来表述类对象本身所具备的元数据, 因为元类中存储着类对象的方法列表,即类方法。
下面将介绍实例对象、类、元类之间的关系,如下图所示:
当向对象发消息时,runtime会在这个对象所属类的实例方法列表中查找消息对应的方法实现(IMP)。但当向类发送消息时,runtime就会在这个类的元类(meta class)方法列表里查找。所有的元类(meta class),包括根类(Root class),父类(Superclass),子类(Subclass)的isa
最终都指向根类(Root class)的元类(meta class),这样能够形成一个闭环。可以总结为:
- 当发送消息给实例对象时,runtime函数会在此实例对象所属类的实例方法列表中查找方法实现(IMP)并调用,此为实例方法调用。
- 当发送消息给类对象时,runtime函数会在此实例对象所属类的元类的方法列表中查找方法实例(IMP)并调用,此为类方法调用。
延伸扩展
方法的调用流程
OC的方法调用本质上是给对象发送消息,即调用runtime的消息发送函数,如下所示:
/*
param: self(id) 接收消息的对象,
param: op(SEL) 发送的消息,即方法签名(@selector(method_name))
*/
id _Nullable objc_msgSend(id _Nullable self, SEL _Nonnull op, ...)
通过对objc_msgSend()
函数的源码分析,总结以下调用流程:
- 检查 selector 是否需要忽略
- 检查 target 是否为 nil,如果是 nil 就直接 cleanup,然后 return
- 在 target 的 Class 中根据 selector 去找 IMP
寻找IMP的流程
- 在当前 class 的方法缓存里寻找(cache methodLists)
- 找到了跳到对应的方法实现,没找到继续往下执行
- 从当前 class 的 方法列表里查找(methodLists),找到了添加到缓存列表里,然后跳转到对应的方法实现;没找到继续往下执行
- 从 superClass 的缓存列表和方法列表里查找,直到找到基类为止
- 以上步骤还找不到 IMP,则进入消息动态处理和消息转发(
objc_msgForward
)流程,详见这篇文章
我们从objc-class.mm
官方源码中找到了寻找IMP的实现过程,如下:
IMP class_getMethodImplementation(Class cls, SEL sel)
{
IMP imp;
if (!cls || !sel) return nil;
imp = lookUpImpOrNil(cls, sel, nil,
YES/*initialize*/, YES/*cache*/, YES/*resolver*/);
// Translate forwarding function to C-callable external version
if (!imp) {
return _objc_msgForward;
}
return imp;
}