在 Objective-C中使用发送消息的形式来调用方法,其中涉及到 Runtime库中定义的 SEL、 IMP、 Method,它们分别表示什么,以及它们之间的关系。

SEL(selector)

SEL:是一个指向 objc_selector 结构体的指针,代表方法的选择器。在运行时,方法选择器用来表示方法的名字,一个方法选择器就是一个C字符串,在OC的运行时被注册。编译器生成选择器在类加载时由运行时自动映射。

结构体指针指向的地址其实就是第一个成员的地址。因此,objc_selector结构体内部的第一个成员很有可能就是一个 char * 类型的字符串,保存着 selector 的名字。

  1. typedef struct objc_selector *SEL;
  1. // 获得SEL的三种方式, 所有的SEL创建都是通过 sel_registerName 函数。
  2. SEL selA = @selector(setTitle:);
  3. SEL selB = sel_registerName("setTitle:");
  4. SEL selC = NSSelectorFromString(@"setTitle:");
  5. // NSSelectorFromString() 和 method_getName()的底层实现都是 sel_registername
  6. // @selector()可以通过clang指令,编译成C++源码,底层仍然是使用的函数 sel_registername

IMP(implementation)

指向方法实现的首地址的指针(函数指针)。源码里实现如下:(可以看得出来是对方法类型起了一个别名。)

  1. // 第一个参数是指向self的指针(实例的内存<实例方法> 或 元类的指针<类方法>);
  2. // 第二个参数是方法的选择器;
  3. // 其他的,是方法的参数
  4. typedef id _Nullable (*IMP)(id _Nonnull, SEL _Nonnull, ...);
  1. // 返回方法的具体实现
  2. IMP class_getMethodImplementation(Class cls, SEL name);
  3. IMP class_getMethodImplementation_stret(Class cls, SEL name);
  4. - (IMP)methodForSelector:(SEL)aSelector;
  5. + (IMP)instanceMethodForSelector:(SEL)aSelector;
  6. // 类实例是否响应指定的selector
  7. BOOL class_respondsToSelector(Class cls, SEL sel);
  1. // 可以直接绕过Runtime的消息传递机制,直接执行IMP指向的函数了。
  2. // 省去了一些列的查找,直接向对象发送消息,效率会高一些
  3. IMP imp = [[objc Class] instanceMethodForSelector:SEL];
  4. // result保存方法的返回值
  5. // id表示调用这个方法的对象,SEL是Method的选择器,argument是方法的参数。
  6. id result = imp(id, SEL, argument);

Method

  1. typedef struct objc_method *Method;
  2. struct objc_method {
  3. SEL _Nonnull method_name;
  4. char * _Nullable method_types;
  5. IMP _Nonnull method_imp;
  6. }
  • method_name:SEL类型(选择器),表示方法名的字符串。
  • method_types:char*类型的,表示方法的类型;包含返回值和参数的类型。
  • method_imp:IMP类型,指向方法实现地址的指针。
  1. 方法操作主要有以下函数:
  2. BOOL class_addMethod(Class cls, SEL name, IMP imp, const char *types); // 添加方法
  3. Method class_getInstanceMethod(Class cls, SEL name); // 获取实例方法
  4. Method class_getClassMethod(Class cls, SEL name); // 获取类方法
  5. Method *class_copyMethodList(Class cls, unsigned int *outCount); // 获取所有方法的数组
  6. // 替代方法的实现
  7. IMP class_replaceMethod(Class cls, SEL name, IMP imp, const char *types);
  8. // 交换两个方法的实现
  9. 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的速度就会非常快