iOS中的class,SEL,IMP
最近在看一些底层的东西,之前一直看到SEL,IMP
等概念也不曾深究,今天看了很多资料,就顺手整理一下,讲的不够细致和准确也希望大家可以评论提出来,共同成长。
什么是class
在说class
定义之前我们不妨先打开#import<objc/objc.h>
文件看一下它的代码
#if !OBJC_TYPES_DEFINED
/// An opaque type that represents an Objective-C class.
typedef struct objc_class *Class;
/// Represents an instance of a class.
struct objc_object {
Class _Nonnull isa OBJC_ISA_AVAILABILITY;
};
/// A pointer to an instance of a class.
typedef struct objc_object *id;
#endif
我们可以看到这是一个struct objc_object
结构体,这个结构体的指针也就是我们开发中经常用到的id
,这个结构体内只有一个成员变量,Class
类型的isa
,我们再点进去看看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 *` */
可以看到这个class
指向了一个叫做objc_class
的结构体,所以由此我们可以知道Class
被定义为一个指向objc_class
的结构体指针,我们再看看objc_class
结构体的内容,这时候我们就又要推出一个概念叫做元数据(metadata)
。
元数据
就是objc_class
内存放的内容,通过成员变量的名字我们可以大致推测,这里面存放了指向父类的指针super_class、类的名字、版本、实例大小、实例变量列表、方法列表(下面会说到)、缓存、遵守的协议列表等
,从这些成员变量中我们可以看出这些成员变量就是一个实例对象构成的基本要素,所以我们可以知道类本身也是一个对象,我们称之为类对象。
而类对象里面也有一个class
类型的变量isa
,这个isa
又指向哪里呢,这时候就又有一个概念叫做元类(metaclass)
,元类中保存了创建类对象以及类方法所需的所有信息。
我们可以通过下边这张图来梳理一下这个关系
由此我们可以知道,一个对象实例可以通过
isa
访问类对象
,而类对象
又是通过isa
访问其元类,superclass
则用于访问其父类
,整个逻辑形成了一个完美的循环图。
什么是SEL
上面我们了解了class,元类
等概念,接下来我们在看SEL
的概念前先了解一下上文提到过的方法列表。
在上面class
讲解中我们提到了一个概念叫做方法列表struct objc_method_list * _Nullable * _Nullable methodLists
,方法列表是objc_class
中的一个成员变量,我们可以结合上文讲过的知识抽象出一个概念图
通过这张图我们是不是也更加理解了上文讲过的class
的内容,圆形所代表的实例对象
的第一个实例变量
为isa
,它指向该类的类结构 The object’s class
。而该类结构有一个指向其父类类结构的指针superclass
, 以及自身消息名称(selector)/实现地址(address)的方法链表
。selector
就是指 Method
的SEL
, address
就是指Method
的 IMP
。
理解了这个结构之后我们再来看看方法链表的代码
typedef struct objc_method *Method;
typedef struct objc_ method {
SEL method_name;
char *method_types;
IMP method_imp;
};
方法链表里面存储的是Method
类型的,一个方法 Method
,其包含一个方法选标 SEL
– 表示该方法的名称,一个types
– 表示该方法参数的类型,一个 IMP
- 指向该方法的具体实现的函数指针。
好现在我们再来看看什么是SEL
,SEL
的定义为:
typedef struct objc_selector *SEL;
它是一个指向 objc_selector
指针,表示方法的名字/签名,SEL
可以等同与C语言中的函数指针
,在C语言中可以直接把函数名
赋值给函数指针
,而OC中只能做一个@selector
来取,它的结果就是一个SEL
类型,本质就是类方法的编号,名字。
不同的类可以拥有相同的selector
,这个没有问题,因为不同类的实例对象performSelector
相同的selector
时,会在各自的消息选标(selector)/实现地址(address) 方法链表
中根据 selector
去查找具体的方法实现IMP
, 然后用这个方法实现去执行具体的实现代码。这是一个动态绑定
的过程,在编译的时候,我们不知道最终会执行哪一些代码,只有在执行的时候,通过selector
去查询,我们才能确定具体的执行代码。
什么是IMP
typedef id (*IMP)(id, SEL, ...);
在讲解SEL
时我们说到方法列表中的实现地址adress就是IMP
,我们就很清楚地知道 IMP
的含义:IMP
是一个函数指针
,这个被指向的函数包含一个接收消息的对象id(self 指针)
, 调用方法的选标 SEL (方法名)
,以及不定个数的方法参数
,并返回一个id
。也就是说IMP
是消息最终调用的执行代码,是方法真正的实现代码 。我们可以像在C语言里面一样使用这个函数指针
。
NSObject
类中的methodForSelector:
方法就是这样一个获取指向方法实现IMP
的指针,methodForSelector:
返回的指针和赋值的变量类型必须完全一致,包括方法的参数类型和返回值类型。
void (*setter)(id, SEL, BOOL);
int i;
setter = (void(*)(id, SEL, BOOL))[target methodForSelector:@selector(setFilled:)];
for (i = 0; i < 1000; i++){
setter(targetList[i], @selector(setFilled:), YES);
}
使用methodForSelector:
来避免动态绑定将减少大部分消息的开销,但是这只有在指定的消息被重复发送很多次时才有意义,例如上面的for循环。
最后说一点扩展,在iOS中方法的调用都是在运行时动态绑定selector和address
,所以我们经常说的runtime
正是利用了这一点,通过改变SEL所绑定的IMP
来进行方法的替换,还有在现在的iOS的ARC机制
中编译器会自动替我们执行autorelease
这个方法,由于会大量使用到autorelease
方法,所以避免消耗内存,在执行时使用的便是methodForSelector
。