iOS 消息转发机制

当我们像一个对象发送消息[Receiver message],Receiver没有实现该消息,即[Receiver respondsToSelector:SEL]返回为NO情况下,其实系统不会立刻出现cash,这时Runtime system会对message进行转发。转发之后,如果该消息依然没有被执行就会出现Cash!Runtime System为我们提供了三种解决这种给对象发送没有实现消息方案。

消息转发机制基本上分为三个步骤:

1. 动态方法解析

2. 备用接收者

3. 完整转发

我们可以通过控制这三个步骤其中一环来解决这一个问题

特别注意:如果是正常类的消息,是不会走到这三个步骤的。所以走到这三个不步骤的前提条件已经确定该消息为未知消息


这篇博客的前置知识点是 OC 的消息传递机制,如果你对此还不了解,请先学习之,再来看这篇。这篇博客我尝试用口语的方式像讲述 PPT 一样给大家讲述这个知识点。

我们来思考一个问题,如果对象在收到无法解读的消息时,会发生什么?例如,我们实现一个 viewcontroller,其中并没有一个成员方法名为『setText:』,当编写这条语句时

[selfsetText:@"你好"];

示例

由于 OC 是一门动态语言,在编译期只是显示一条 warning,而不是阻止运行的 error。如果忽略 warning 运行,程序会 crash,在控制台会显示类似

unrecognized selector sent to instance0x7f931a4180d0

的报错信息。

unrecognized selector

消息被发送给了不能处理它的对象。我们学习 iOS 的消息转发机制可不是为了故意造这样的 crash 玩,说上面的这个例子,是为了说明如果我们不通过消息转发机制做任何事情的话,系统最终会以 crash 结束。等等,刚才我们说到 OC 是一门动态语言,那么是否可以在运行期做一些事来让 crash 不会发生呢?

消息转发机制就是来干这件事的,在运行期通过3个『接盘侠』方法,给对象和消息更多的机会来完成成功的调用,而不是直接 crash。

一号接盘侠

第一个接盘侠代表动态方法解析阶段,对应的具体方法是+(BOOL)resolveInstanceMethod:(SEL)sel 和+(BOOL)resolveClassMethod:(SEL)sel,当方法是实例方法时调用前者,当方法为类方法时,调用后者。这个方法设计的目的是为了给类利用 class_addMethod 添加方法的机会。

看下面这个示例,MyTestObject类重写了第一个接盘侠方法,可以看到这个方法传入一个 selector,返回 BOOL 类型。被传入的 selector 就是未被处理的方法,在一号接盘侠方法中,判断若方法名为 XXX 则给这个类添加同名的方法,把方法的实现指向跟 XXX 名字不一致的 AAA,并返回 YES。若 selector 名字不是 XXX,就返回父类。

resolveInstanceMethod

通过这个示例,可以看出,我们可以通过一号接盘侠方法让 方法名和方法实现在运行期任意搭配。

再说一下这个返回值,其实可以试验一下,无论返回 YES 还是 NO,系统都会尝试用 SEL 来寻找 IMP,如果找到函数实现,则执行,所以无论返回 YES\NO都会进入二号接盘侠方法。

二号接盘侠

第二个阶段是备援接收者阶段,对象的具体方法是-(id)forwardingTargetForSelector:(SEL)aSelector ,此时,运行时询问能否把消息转给其他接收者处理,也就是此时系统给了个将这个 SEL 转给其他对象的机会。我们继续来研究下参数和返回值,参数和一号接盘侠一样,都是 selector,返回值是 id 类型,当返回 非self\非nil 时,消息被转给新对象执行。

forwardingTargetForSelector

三号接盘侠

第三个阶段是完整消息转发阶段,对应方法-(void)forwardInvocation:(NSInvocation *)anInvocation,这是消息转发流程的最后一个环节。参数 anInvocation 中包含未处理消息的各种信息(selector\target\参数...)。在这个方法中,可以把 anInvocation 转发给多个对象,与二号接盘侠不同,二号只能转给一个对象。

forwardInvocation

如果上述3个方法都没有来处理这个消息,就会进入 NSObject 的-(void)doesNotRecognizeSelector:(SEL)aSelector方法中,抛出异常。等等,为什么我们不能通过给 NSObject 创建一个 category,重写这个方法,在这里处理消息未被处理的情况呀?在苹果的官方文档中,明确提到,“一定不能让这个函数就这么结束掉,必须抛出异常”。除了听官方文档的话,其实在分类中通过重写该方法处理各种消息未被处理的情况,会让这个分类的方法特别长,不利于维护。而且还有个原因,明明方法名叫『无法识别 selector』,其中却是一大堆处理该情况的代码,也很奇怪。

doesNotRecognizeSelector

总结

总结一下整个消息转发的流程:

消息转发的流程

可以通过重写3个接盘侠方法,在其中打断点来验证执行顺序。

断点验证顺序

总结:

在一个函数找不到时,OC提供了三种方式去补救:

1、调用resolveInstanceMethod给个机会让类添加这个实现这个函数

2、调用forwardingTargetForSelector让别的对象去执行这个函数

3、调用forwardInvocation(函数执行器)灵活的将目标函数以其他形式执行。

如果都不中,调用doesNotRecognizeSelector抛出异常。

疑问

Q1:那我们只用最后一个接盘侠方法多好啊,为什么还需要前2个呢?

其实还与这3个方法的用途不同有关:

运行期添加方法,用1;

转发给另1个对象、改变方法时,用2;

需要转发给多个对象时,用3;

而且,步骤越往后,处理消息的代价越大,到最后一个阶段时,都创建了 NSInvocation 对象了。

Q2:消息转发有哪些应用场景呢?

可以在运行期再加入某方法,例如 Teacher 类里有teach方法,DrugDealer 类里有letsCook方法,通过一号接盘侠方法,我们可以在运行期把 saleDrug 偷摸加到 teacher 的方法列表中,让 teacher 具备贩毒的功能,[teacher  guessWhatHeDo],实际调用的是[teacher letsCook],唉呀妈呀,绝命毒师啊。

把方法转给其他对象处理,再举个例子,还是 Teacher 类(博主跟老师有仇吗...),[teacher letsCook],可以把对象在运行期换为drugDealer。再来一个 Cook 类,也有 letsCook 方法,但这次这方法不是 cook 毒品,而是 cook 菜。因此既可以通过[teacher letsCook] 实现[drugDealer letsCook],也可以实现[cook letsCook]。相当于 OC 实现了多重继承,虽然有点不太恰当...

注意

respondsToSelector我们再熟悉不过了,用来检查某对象是否实现了某方法。此函数通常是不需要重载的,但是在动态实现了查找过程后,需要重载此函数让对外接口查找动态实现函数的时候返回YES,保证对外接口的行为统一。

respondsToSelector

最后说一下 warning 的事。编译器很好心的报的那个 warning 咋办呢,不管那个小黄条不是一个爱整洁的程序员的风格,所以我们要想办法把它去掉。

有两种方法,第一种比较暴力,通过在配置文件中把 Complier Flag 加-w,对该类去除所有 warning。

去掉所有warning

第二种是推荐的做法,在 xcode 的 error 面板对 warning 右键-Reveal in Log,这里有个小 bug,如果这个选项不可选择,需要你重新 build 一下就可选了,

小 Bug

在右侧,可以看到这个warning 的名称,

如何看warning名称

所以用这个宏把出现 warning 的代码包围起来,就可以让编译器不再报错:

#pragmaclang diagnostic push#pragmaclang diagnostic ignored"-Wobjc-method-access"[self setText:@"你好"];#pragmaclang diagnostic pop

文/毁小慕(简书作者)

原文链接:http://www.jianshu.com/p/fa29c920409d

著作权归作者所有,转载请联系作者获得授权,并标注“简书作者”。

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

推荐阅读更多精彩内容