消息转发机制

前言

今天我们再来通过另外一个机制来感受一下OC的动态特性吧,那就是OC的消息转发机制

在之前的不一样的OC中我们有提到,OC是消息型语言,OC中的方法调用其实只是传递消息而已,编译器并不能决定程序真正执行的到底是哪段代码,这个工作,需要运行时系统来完成.

那我们今天要讨论的消息转发又是什么呢?

既然OC的方法调用实际上都是消息传递,那传递的消息内容和时机其实编译器并不能完全控制,这时我们是不是能够联想到这样的场景:一个对象接受了一个编译器控制外的消息,这个对象对这个消息无从下手,他并不具备解决这个消息的能力(没有实现相应方法),这个时候该怎么办呢?

当遇到这种情况的时候,我们的消息转发机制就被触发了,系统将利用这套机制竭力帮我们控制这个糟糕的局面,不至于让程序崩溃,但是如果消息转发机制竭尽全力通过各种办法也无法解决这个意料之外的消息时,那么程序就会崩溃,并产生那个经典的崩溃日志:


unrecognized selector sent to instance 0x60000000b480

消息转发机制的三部曲

消息转发机制在极力为我们收拾残局的时候,会有三步措施,如果在最坏的情况下,这三个步骤将会按顺序依次执行,如果这三个步骤都无法挽回,那么程序将会崩溃,值得一提的是:如果消息转发机制的某一个步骤就能解决这个问题,那么下一个步骤就不会执行了,消息转发机制就到此为止了(事情都解决了,当然不需要下一步了)

我们现在用一个简单的例子来触发消息转发机制,并利用消息转发机制解决问题

我新建了一个叫做Person的类,并在它的.h文件里为Person声明了一个实例方法:run

以下是.h文件的内容.


#import <Foundation/Foundation.h>

@interface Person : NSObject

-(void)run;

@end

大家猜猜我在.m文件里做了什么?以下是.m文件的内容:

#import "Person.h"

@implementation Person

@end

哈哈,我什么都没做,甚至没有对.h中的run方法写实现.

我为什么要这么做呢?这样我们就可以欺骗编译器,在调用这个方法的时候,编译器默认我们已经对这个方法写了实现,他就会让我们舒舒服服的调用这个方法,并不会报错,组织我们队这个方法的调用

那接下来,我们在控制器中新建一个person对象,并调用这个方法的run方法

Person *p1=[Person new];
    
    [p1 run];

大家应该猜的到会发生什么了吧,我们能够成功编译并运行这个程序,但是当代码执行到了run方法时,程序崩溃了,并报了我们常见的那个错误unrecognized selector sent to instance 0x60000000b480

大家一定想知道这其中到底发生了什么事情:当run方法被调用时,运行时系统就会对p1这个对象发送一条run的消息,意料之中的是p1这个对象并不能对相应这个消息,因为我们没有写run的实现方法,那接下来就会触发消息转发机制了,那既然触发了消息转发机制,为什么还是崩溃了呢,因为,这就是那种最坏的情况,消息转发机制三个步骤依次执行却依然没有挽回崩溃的结果.

那么我们快看一看怎么让消息转发机制发挥作用吧.

步骤一:能不能动态添加一个函数解决这个消息啊

在这一步中,面对无法解决的消息,消息转发机制首先做的是,看看能否动态添加一个方法实现,来解决这个消息.

如果是实例方法,系统会调用这个这个实例所属类的类方法:

+(BOOL)resolveInstanceMethod:(SEL)sel

如果是类方法:

+(BOOL)resolveClassMethod:(SEL)sel

这对方法如何使用呢?我们来看:

我在person类的.m文件中写了如下代码:

+(BOOL)resolveInstanceMethod:(SEL)sel
{

    if (sel==@selector(run)) {
        class_addMethod(self, sel, (IMP)run , "v@:");
        return YES;
    }
    return [super resolveInstanceMethod:sel];
}

void run (id self,SEL _cmd)
{
    NSLog(@"fdfsfaf");
}

我们写了一个run的c语言函数,当消息机制触发时,selector为run时那么就会利用运行时库动态添加一个方法,而这个方法的函数实现是我们用C写的一个函数,这个函数的两个参数为self,和_cmd,因为OC方法的本质就是至少包含两个参数的C函数这两个参数便是隐藏的self,和_cmd,前者是消息接受者,后者是一个SEL指针

通过这样的操作,系统就为我们通过动态添加一个方法的方式来解决了这个不能响应的消息
这也是消息转发机制的第一步,如果这一步并没有解决这个问题,那便要有下面的步骤了.

步骤二:能不能找别人处理这个消息啊

当上一步没有解决这个问题的时候,这一步,系统将会通过寻找有没有别的对象可以帮忙处理这条消息.

系统会调用如下方法:

-(id)forwardingTargetForSelector:(SEL)aSelector
{
    return [RunPerson new];
}

大家可以看到,我在这个方法中返回了一个新的类RunPerson的实例,而在这个类的.m文件中,我写了如下实现:

-(void)run
{
    NSLog(@"我是会跑的人,我能跑!");
}

通过返回一个新的类,我们把这个消息转发给了一个其它对象,让这个对象来代替我们解决这个消息.

步骤三: 完整的消息转发

在这一步中,消息转发机制将进行最后一步操作

我在Person的.m文件中写了如下代码,将会使得run消息,在消息转发机制最后一步得到处理

-(NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
{
    if (aSelector==@selector(run)) {
        return [NSMethodSignature signatureWithObjCTypes:"v@:"];
    }
    return [super methodSignatureForSelector: aSelector];
}

-(void)forwardInvocation:(NSInvocation *)anInvocation
{
    SEL selector =[anInvocation selector];
    
    RunPerson *RP1=[RunPerson new];
    RunPerson *RP2=[RunPerson new];
    
    if ([RP1 respondsToSelector:selector]) {
        
        [anInvocation invokeWithTarget:RP1];
    }
    if ([RP2 respondsToSelector:selector]) {
        
        [anInvocation invokeWithTarget:RP2];
    }    
}

第一个方法返回的是一个方法签名,而代码中的v@:表示这个函数的性质,v代表返回值为void,@代表self,:代表_cmd;

当有了方法签名,消息转发机制将会调用-(void)forwardInvocation:(NSInvocation )anInvocation方法,这个方法的参数anInvocation*为这个消息的全部内容,包括调用者,参数等等.

在这个方法里我们初始化了两个RunPerson实例,并把这个Invocation转发给了这两个RunPerson实例,这样,这两个RunPerson对象就都会对这个消息进行处理了
这也是与第二个步骤最大的区别,可以转发给多个对象进行处理.

到此,消息转发的三个步骤就结束了,如果到第三步还没有解决这个消息,那么程序就会崩溃了.

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

推荐阅读更多精彩内容