前两篇介绍了类与对象、成员变量&属性&关联对象的相关知识,本篇我们将开始讲解Runtime中最有意思的一部分内容:消息处理机制。我们从一个示例开始。
在OC中,我们使用下面这种方式来调用方法:
GofTest *test = [[GofTest alloc] init];
[test eat];
对上面的方法调用,我们用Runtime的消息发送机制改造一下:
id test = objc_msgSend(objc_getClass("GofTest"), sel_registerName("alloc")); objc_msgSend(test, sel_registerName("init")); objc_msgSend(test, sel_registerName("eat"));
对于上面的结果,我们来验证一下:
//cd到目录,执行如下指令 clang -rewrite-objc main.m
上面的Clang指令可以将 Objetive-C 的源码改写成 C 语言的,打开目录,可以看到多了一个main.cpp文件。打开main.cpp文件,这个文件有将近10W行代码,我们翻到最下面,看main函数:
#ifndef _REWRITER_typedef_GofTest #define _REWRITER_typedef_GofTest typedef struct objc_object GofTest; typedef struct {} _objc_exc_GofTest; #endif struct GofTest_IMPL { struct NSObject_IMPL NSObject_IVARS; }; // - (void)eat; /* @end */ int main(int argc, char * argv[]) { /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; GofTest *test = ((GofTest *(*)(id, SEL))(void *)objc_msgSend)((id)((GofTest *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("GofTest"), sel_registerName("alloc")), sel_registerName("init")); ((void (*)(id, SEL))(void *)objc_msgSend)((id)test, sel_registerName("eat")); } return 0; }
去掉类型强制转换,我们再来分析一下:
int main(int argc, char * argv[]) { { __AtAutoreleasePool __autoreleasepool; GofTest *test = objc_msgSend(objc_msgSend(objc_getClass("GofTest"), sel_registerName("alloc")), sel_registerName("init")); objc_msgSend(test, sel_registerName("eat")); } return 0; }
通过查看C语言代码,我们得出一个初步结论:使用objc_msgSend函数,给objc_getClass函数实例化的对象发送sel_registerName获取到的方法的消息。
在Runtime中,objc_msgSend
这个方法是用汇编实现的,这是为了提升方法执行时的效率,因为几乎每个方法调用在内部都是通过objc_msgSend
来完成的。为了进一步提升这个方法的效率,苹果还对每个类的方法做了缓存,当一个方法被调用过之后,它的IMP会被缓存在cache列表里,这样当再次调用该方法时,就可以很快地从缓存中取出,而不用再走一次完整的查找流程。
上面示例涉及到的SEL、objc_msgSend等内容,我们在下面会一一介绍。
【说明】:从Xcode 5.0开始,苹果不建议直接使用底层的消息发送机制,因此需要关掉下面的这个开关。
看完上面的示例后,我们来了解几个基本类型。
1.基本类型
1.1SEL
SEL又叫选择器,是表示一个方法的selector
的指针,其定义如下:
typedef struct objc_selector *SEL; struct objc_selector { void *sel_id; const char *sel_types; };
方法的selector
用于表示运行时方法的名字。Objective-C在编译时,会根据每一个方法的名字、参数序列,生成一个唯一的整型标识(Int类型的标识),这个标识就是SEL
。
SEL sel = @selector(alloc); NSLog(@"sel : %p", sel); //打印结果 sel : 0x103e719b2
两个类之间,只要方法名相同,那么方法的SEL就是一样的。每一个方法都对应着一个SEL,所以在Objective-C同一个类(及类的继承体系)中,不能存在2个同名的方法,即使参数类型不同也不行。相同的方法只能对应一个SEL。这也就导致Objective-C在处理相同方法名且参数个数相同但类型不同的方法方面的能力很差。
当然,不同的类可以拥有相同的Selector,这个没有问题。不同的类的实例对象执行相同的Selector时,会在各自的方法列表中根据Selector去寻找自己对应的IMP。
工程中的所有SEL组成一个Set集合,如果我们想到这个方法集合中查找某个方法时,只需要找到这个方法对应的SEL就行了,SEL实际上就是根据方法名hash化了的一个字符串,而对于字符串的比较仅仅需要比较它们的地址就可以了。
本质上,SEL只是一个指向方法的指针(准确的说,只是一个根据方法名hash化了的KEY值,能唯一代表一个方法),它的存在只是为了加快方法的查询速度。
我们可以通过下面三种方法来获取SEL:
//编译器提供 @selector() //Runtime方法 OBJC_EXPORT SEL sel_registerName(const char *str) OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0); //OC方法 FOUNDATION_EXPORT SEL NSSelectorFromString(NSString *aSelectorName);
示例如下:
SEL sel = @selector(alloc); NSLog(@"sel : %p", sel); //打印结果 sel : 0x103e719b2 id test = objc_msgSend(objc_getClass("GofTest"), sel_registerName("alloc")); objc_msgSend(test, sel_registerName("init")); objc_msgSend(test, NSSelectorFromString(@"eat"));
1.2IMP
IMP实际上是一个函数指针,指向方法实现的首地址。其定义如下:
#if !OBJC_OLD_DISPATCH_PROTOTYPES typedef void (*IMP)(void /* id, SEL, ... */ ); #else typedef id (*IMP)(id, SEL, ...); #endif
这个函数的第一个参数是指向self的指针(如果是实例方法,则是类实例的内存地址;如果是类方法,则是指向元类的指针),第二个参数是方法选择器(selector),接下来是方法的实际参数列表。
前面介绍过的SEL就是为了查找方法的最终实现IMP的。由于每一个方法对应唯一的SEL,因此我们可以通过SEL方便快速准确地获得它所对应的IMP,查找过程将在下面讨论。取得IMP后,我们就获得了执行这个方法代码的入口点,此时,我们就可以像调用普通的C语言函数一样来使用这个函数指针了。
通过取得IMP,我们可以跳过Runtime的消息传递机制,直接执行IMP指向的函数实现,这样省去了Runtime消息传递过程中所做的一系列查找操作,会比直接向对象发送消息高效一些。
1.3Method
Method用于表示类定义的方法,定义如下:
typedef struct objc_method *Method; struct objc_method { SEL method_name OBJC2_UNAVAILABLE; //方法名 char *method_types OBJC2_UNAVAILABLE; IMP method_imp OBJC2_UNAVAILABLE; //方法实现 }
从定义可以看到,结构体中包含一个SEL和IMP,实际上相当于在SEL和IMP之间作了一个映射。有个SEL,我们便可以找到对应的IMP,从而调用方法的实现代码。
2.相关操作函数
Runtime提供了一系列的方法来处理与方法相关的操作。包括方法本身及SEL。
//调用指定方法的实现 OBJC_EXPORT id method_invoke(id receiver, Method m, ...) OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0); //调用返回一个数据结构的方法的实现 OBJC_EXPORT void method_invoke_stret(id receiver, Method m, ...) OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0) OBJC_ARM64_UNAVAILABLE; //获取方法名 OBJC_EXPORT SEL method_getName(Method m) OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0); //返回方法的实现 OBJC_EXPORT IMP method_getImplementation(Method m) OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0); //获取描述方法参数和返回值类型的字符串 OBJC_EXPORT const char *method_getTypeEncoding(Method m) OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0); //获取方法的返回值类型的字符串 char * method_copyReturnType ( Method m ); //获取方法的指定位置参数的类型字符串 OBJC_EXPORT char *method_copyArgumentType(Method m, unsigned int index) OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0); //通过引用返回方法的返回值类型字符串 OBJC_EXPORT void method_getReturnType(Method m, char *dst, size_t dst_len) OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0); //返回方法的参数的个数 OBJC_EXPORT unsigned int method_getNumberOfArguments(Method m) OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0); //通过引用返回方法指定位置参数的类型字符串 OBJC_EXPORT void method_getArgumentType(Method m, unsigned int index, char *dst, size_t dst_len) OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0); //返回指定方法的方法描述结构体 OBJC_EXPORT struct objc_method_description *method_getDescription(Method m) OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0); //设置方法的实现 OBJC_EXPORT IMP method_setImplementation(Method m, IMP imp) OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0); //交换两个方法的实现 OBJC_EXPORT void method_exchangeImplementations(Method m1, Method m2) OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0); //返回给定选择器指定的方法的名称 OBJC_EXPORT const char *sel_getName(SEL sel) OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0); //在Objective-C Runtime系统中注册一个方法,将方法名映射到一个选择器,并返回这个选择器。
//【说明】:所谓的注册,就是把一个methodName映射到一个selector并返回selecor; 如果已经注册则直接返回selector OBJC_EXPORT SEL sel_registerName(const char *str) OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0); //在Objective-C Runtime系统中注册一个方法(objc.h) OBJC_EXPORT SEL sel_getUid(const char *str) OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0); //比较两个选择器 OBJC_EXPORT BOOL sel_isEqual(SEL lhs, SEL rhs) OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0);
使用示例如下所示:
GofPerson *person = [[GofPerson alloc] init]; Class objectClsObj = object_getClass(person); //1.注册方法(这里的注册,就是把一个methodName映射到一个selector并返回selecor; 如果已经注册则直接返回selector) //留意一下,如果方法名称相同,则返回的SEL也是一样的 SEL sel_register = sel_registerName("registerNewMethod"); SEL sel_register2 = sel_getUid("registerNewMethod"); //2.比较两个SEL if (sel_isEqual(sel_register, sel_register2)) { NSLog(@"相同的SEL"); } else { NSLog(@"不同的SEL"); } //3.获取实例方法 Method instanceMethod = class_getInstanceMethod([GofPerson class], @selector(doWork:)); //如果指定的SEL不存在,那么返回Nil //objc_msgSend、objc_msgSendSuper、method_invoke(有返回值)都用于消息发送 //objc_msgSend_stret、objc_msgSend_fpret函数是这三者的变体 //当返回的是结构体、浮点数时,会调用类似_stret、_fpret的方法 NSString *strRet = method_invoke(person, instanceMethod, @"看书"); //调用方法 NSLog(@"method_invoke 执行方法结果:%@", strRet); SEL sel_instanceMethod = method_getName(instanceMethod); //获取SEL NSLog(@"实例方法名称:%s", sel_getName(sel_instanceMethod)); //获取方法名称 //4.获取类方法 Method classMethod = class_getClassMethod(objectClsObj, @selector(createPersonWithName:age:)); SEL sel_classMethod = method_getName(classMethod); //获取SEL NSLog(@"类方法名称:%s", sel_getName(sel_classMethod)); //获取方法名称 //5.获取实例方法列表 unsigned int count_instanceMethod = 0; Method *instanceMethodList = class_copyMethodList([GofPerson class], &count_instanceMethod); for (int i = 0; i < count_instanceMethod; i++) { Method method = instanceMethodList[i]; SEL sel_Method = method_getName(method); const char *returnType = method_copyReturnType(method); //方法返回值类型 NSLog(@"实例方法%d:%@ 返回值类型:%s", i, NSStringFromSelector(sel_Method), returnType); } free(instanceMethodList); //6.获取类方法列表 unsigned int count_classMethod = 0; Method *classMethodList = class_copyMethodList(object_getClass([GofPerson class]), &count_classMethod); for (int i = 0; i < count_classMethod; i++) { Method method = classMethodList[i]; SEL sel_Method = method_getName(method); const char *methodType = method_getTypeEncoding(method);// 获取方法参数类型和返回类型 NSLog(@"类方法%d:%@ 方法类型:%s", i, NSStringFromSelector(sel_Method), methodType); } free(classMethodList); //7.获取方法相关信息 Method methodTest = class_getClassMethod(objectClsObj, @selector(createPersonWithName:age:)); //获取方法返回值类型 const char* method_ReturnType = method_copyReturnType(methodTest); NSLog(@"方法返回值类型:%@",[NSString stringWithUTF8String:method_ReturnType]); //获取方法参数个数 unsigned int numberOfArguments = method_getNumberOfArguments(methodTest); NSLog(@"方法参数个数:%d",numberOfArguments); char argName[512] = {}; for ( int i = 0; i < numberOfArguments ; i ++) { //获取方法参数类型方式1 method_getArgumentType(methodTest, i, argName, 512); NSLog(@"方式1统计参数%d类型:%s", i, argName); memset(argName, '