iOS runtime之消息机制

我们都知道在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如图
selector验证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];
  }
}

总结

下图能够更清晰明确的展示一个消息的整个处理过程:
iOS runtime之消息机制_第1张图片
消息转发流程

你可能感兴趣的:(iOS runtime之消息机制)