我们都知道在OC中调用一个对象的方法会被转化成给一个对象发送消息。如以下的代码调用:
[target doSomeThing:@"param"];
将会被转化成下面这样的C函数调用形式:
objc_msgSend(target, @selector(doSomeThing:), @"param");
那么之后呢,OC是如何处理这个消息的呢?而doSomeThing这个函数的真正实现是在什么时候得到执行的呢?先来熟悉几个有关的概念。
Method
先来看一下Method在runtime中的具体定义:
typedef struct objc_method* Method;
struct objc_method
{
SEL method_name;
char* method_types;
IMP method_imp;
}
method_name的类型为SEL,它的具体作用后面会讲到。
method_types存储方法的参数类型与返回类型,参见下篇文章。
method_imp指向该方法的具体实现,本质上来说它就是一个函数指针。
SEL
SEL又叫做方法选择器,它的具体定义如下:
typedef struct objc_selector* SEL;
可惜我们在源码中不能直接找出objc_selector的具体定义。但我们猜测它本质上就是一个char*指针,可以通过如下代码来验证下:
SEL methodSelector = @selector(doSomeThing:);
const char* name = sel_getName(methodSelector);
NSLog(@"%s", methodSelector);
输出Log如图
SEL是标识一个方法的惟一ID,它存在的目的主要是加快方法查找的速度。
IMP
IMP的具体定义如下:
typedef id (*IMP)(id, SEL, ...);
学过C的同学对这个一定很熟悉,这不就是函数指针吗!对,IMP实质上就是函数指针,指向一个OC函数的具体实现。通过SEL可以找到具体的方法实现IMP,通过IMP就可以实现像调用C函数那样调用OC函数了,代码如下:
//定义一个函数指针
void (*methodImp)(id, SEL);
//通过方法的SEL获取方法的实现IMP,并赋值给函数指针
methodImp = (void(*)(id,SEL))[self methodForSelector:@selector(method)];
//通过函数指针直接调用函数
methodImp(self, @selector(method));
方法解析
上篇文章说到Class类中有个objc_method_list字段,它本质上就是以objc_method为元素的可变数组。那么我们可以就方法查找流程做以下几点推论:
- 先通过对象的isa指针找到它的class
- 然后在class的method_list中查找方法
- 如果没有找到,则继续它的superclass中查找,依此往复,直到找到该方法为止
- 最后执行该方法的IMP实现
这个推论大至是成立的,不过还需要补充的一点是,方法查找过程中,会首先到方法的缓存列表中查找,缓存中找不到后才会到method_list中查找,并且方法执行时,会将方法添加到缓存中,这样后续该方法再次执行时,就可以在缓存中查找到并尽快的得到执行了。这样做的好处是,避免每次方法执行时都去method_list中查找,进而提高函数查询的效率。缓存的定义代码如下:
typedef struct objc_cache* Cache;
struct objc_cache
{
unsigned int mask;
unsigned int occupied;
cache_entry buckets[1];
}
typedef struct
{
SEL name;
void* unused;
IMP imp;
}cache_entry;
动态方法解析与转发
如果开发者调用的是一个末定义的方法,会发生什么呢?当然是抛出一个unrecognized selector send to instance异常了。而在异常招聘之前,runtime提供了三次挽救的机会。
resolveInstanceMethod/resolveClassMethod
这两个方法都是NSObject类中提供的类方法,定义如下:
+(BOOL)resolveInstanceMethod:(SEL)selector;
+(BOOL)resolveClassMethod:(SEL)selector;
它们的主要功能是让我们有机会为一个SEL动态的添加实现的IMP。如果在该方法中添加了实现并返回YES,runtime就会重新启动一次消息发送过程,进而使SEL得到正确的执行。示例代码如下:
void method(id target, SEL selector)
{
NSLog(@"method working");
}
+(BOOL)resolveInstanceMethod:(SEL)aSel
{
if(aSel == @selector(method))
{
class_addMethod([self class], aSel, method, "v@:");
return YES;
}
return [super resolveInstanceMethod:aSel];
}
如果该方法返回NO,就会进入下一个挽救程序的过程了。
forwardTargetForSelector:
该方法提供了将该SEL转发给别的对象执行的机会。只要这个方法返回值不是nil或者self,那么就会像该返回值对象发送消息。示例代码如下:
-(id)forwardTargetForSelector:(SEL)aSel
{
if(aSel == @selector(method))
{
id newTarget = [[MyObject alloc] init];
return newTarget;
}
return [super forwardTargetForSelector:aSel];
}
methodSignatureForSelector:
如果上面两步都没有能够处理SEL,runtime会通过methodSigntureForSelector方法尝试获取本次消息调用的具体环境信息,包括消息的参数与返回值类型。并封装成NSInvocation对象。我们可以在forwardInvocation方法内部对该对象作进一步的处理,并使之能够成功的完成消息处理。如果末能成功获取NSInvocation对象,那么程序就会发送doesNotRecognizeSelector消息抛出unrecognized Selector send to xxx的异常。示例代码如下:
-(void)forwardInvocation:(NSInvocation*)invocation
{
if([otherTarget respondsToSelector:[invocation selector]])
{
[invocation invokeWithTarget:otherTarget];
}
}