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];
  }
}

总结

下图能够更清晰明确的展示一个消息的整个处理过程:
消息转发流程
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,732评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,496评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,264评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,807评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,806评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,675评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,029评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,683评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,704评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,666评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,773评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,413评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,016评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,978评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,204评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,083评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,503评论 2 343

推荐阅读更多精彩内容