在 Objective-C中使用发送消息的形式来调用方法,其中涉及到 Runtime库中定义的 SEL、 IMP、 Method,它们分别表示什么,以及它们之间的关系。
SEL(selector)
SEL:是一个指向 objc_selector
结构体的指针,代表方法的选择器。在运行时,方法选择器用来表示方法的名字,一个方法选择器就是一个C字符串,在OC的运行时被注册。编译器生成选择器在类加载时由运行时自动映射。
结构体指针指向的地址其实就是第一个成员的地址。因此,objc_selector结构体内部的第一个成员很有可能就是一个 char * 类型的字符串,保存着 selector 的名字。
typedef struct objc_selector *SEL;
// 获得SEL的三种方式, 所有的SEL创建都是通过 sel_registerName 函数。
SEL selA = @selector(setTitle:);
SEL selB = sel_registerName("setTitle:");
SEL selC = NSSelectorFromString(@"setTitle:");
// NSSelectorFromString() 和 method_getName()的底层实现都是 sel_registername
// @selector()可以通过clang指令,编译成C++源码,底层仍然是使用的函数 sel_registername
IMP(implementation)
指向方法实现的首地址的指针(函数指针)。源码里实现如下:(可以看得出来是对方法类型起了一个别名。)
// 第一个参数是指向self的指针(实例的内存<实例方法> 或 元类的指针<类方法>);
// 第二个参数是方法的选择器;
// 其他的,是方法的参数
typedef id _Nullable (*IMP)(id _Nonnull, SEL _Nonnull, ...);
// 返回方法的具体实现
IMP class_getMethodImplementation(Class cls, SEL name);
IMP class_getMethodImplementation_stret(Class cls, SEL name);
- (IMP)methodForSelector:(SEL)aSelector;
+ (IMP)instanceMethodForSelector:(SEL)aSelector;
// 类实例是否响应指定的selector
BOOL class_respondsToSelector(Class cls, SEL sel);
// 可以直接绕过Runtime的消息传递机制,直接执行IMP指向的函数了。
// 省去了一些列的查找,直接向对象发送消息,效率会高一些
IMP imp = [[objc Class] instanceMethodForSelector:SEL];
// result保存方法的返回值
// id表示调用这个方法的对象,SEL是Method的选择器,argument是方法的参数。
id result = imp(id, SEL, argument);
Method
typedef struct objc_method *Method;
struct objc_method {
SEL _Nonnull method_name;
char * _Nullable method_types;
IMP _Nonnull method_imp;
}
- method_name:SEL类型(选择器),表示方法名的字符串。
- method_types:char*类型的,表示方法的类型;包含返回值和参数的类型。
- method_imp:IMP类型,指向方法实现地址的指针。
方法操作主要有以下函数:
BOOL class_addMethod(Class cls, SEL name, IMP imp, const char *types); // 添加方法
Method class_getInstanceMethod(Class cls, SEL name); // 获取实例方法
Method class_getClassMethod(Class cls, SEL name); // 获取类方法
Method *class_copyMethodList(Class cls, unsigned int *outCount); // 获取所有方法的数组
// 替代方法的实现
IMP class_replaceMethod(Class cls, SEL name, IMP imp, const char *types);
// 交换两个方法的实现
method_exchangeImplementations(Method m1, Method m2)
总结
- SEL:SEL是一个结构体指针,可以简单理解为一个字符串(Char*类型),表示方法的名字
- IMP:就是指向方法实现首地址的指针
- Method:是一个结构体指针,包含一个SEL表示方法名、一个IMP指向函数的实现地址、一个Char*表示函数的类型(包括返回值和参数类型)
结合类来理解,一个类(Class)持有一系列的方法(Method),在load类时,runtime会将所有方法的选择器(SEL)hash后映射到一个集合(NSSet)中(NSSet里的元素不能重复)。当需要发消息时,会根据选择器(SEL)去查找方法;找到之后,用Method结构体里的函数指针(IMP)去调用方法。这样在运行时查找selecter的速度就会非常快