A股上市公司传智教育(股票代码 003032)旗下技术交流社区北京昌平校区

 找回密码
 加入黑马

QQ登录

只需一步,快速开始

© xqlyn123 中级黑马   /  2015-11-27 23:13  /  1571 人查看  /  15 人回复  /   1 人收藏 转载请遵从CC协议 禁止商业使用本文

25. _objc_msgForward函数是做什么的,直接调用它将会发生什么?
_objc_msgForward是 IMP 类型,用于消息转发的:当向一个对象发送一条消息,但它并没有实现的时候,_objc_msgForward会尝试做消息转发。
我们可以这样创建一个_objc_msgForward对象:
IMP msgForwardIMP = _objc_msgForward;在上篇中的《objc中向一个对象发送消息[obj foo]和objc_msgSend()函数之间有什么关系?》曾提到objc_msgSend在“消息传递”中的作用。在“消息传递”过程中,objc_msgSend的动作比较清晰:首先在 Class 中的缓存查找 IMP (没缓存则初始化缓存),如果没找到,则向父类的 Class 查找。如果一直查找到根类仍旧没有实现,则用_objc_msgForward函数指针代替 IMP 。最后,执行这个 IMP 。
Objective-C运行时是开源的,所以我们可以看到它的实现。打开 Apple Open Source 里Mac代码里的obj包 下载一个最新版本,找到 objc-runtime-new.mm,进入之后搜索_objc_msgForward。
里面有对_objc_msgForward的功能解释:
/************************************************************************ lookUpImpOrForward.* The standard IMP lookup. * initialize==NO tries to avoid +initialize (but sometimes fails)* cache==NO skips optimistic unlocked lookup (but uses cache elsewhere)* Most callers should use initialize==YES and cache==YES.* inst is an instance of cls or a subclass thereof, or nil if none is known. *   If cls is an un-initialized metaclass then a non-nil inst is faster.* May return _objc_msgForward_impcache. IMPs destined for external use *   must be converted to _objc_msgForward or _objc_msgForward_stret.*   If you don't want forwarding at all, use lookUpImpOrNil() instead.**********************************************************************/对 objc-runtime-new.mm文件里与_objc_msgForward有关的三个函数使用伪代码展示下:
//  objc-runtime-new.mm 文件里与 _objc_msgForward 有关的三个函数使用伪代码展示//  Created by https://github.com/ChenYilong//  Copyright (c)  微博@iOS程序犭袁(http://weibo.com/luohanchenyilong/). All rights reserved.//  同时,这也是 obj_msgSend 的实现过程id objc_msgSend(id self, SEL op, ...) {    if (!self) return nil;    IMP imp = class_getMethodImplementation(self->isa, SEL op);    imp(self, op, ...); //调用这个函数,伪代码...}//查找IMPIMP class_getMethodImplementation(Class cls, SEL sel) {    if (!cls || !sel) return nil;    IMP imp = lookUpImpOrNil(cls, sel);    if (!imp) return _objc_msgForward; //_objc_msgForward 用于消息转发    return imp;}IMP lookUpImpOrNil(Class cls, SEL sel) {    if (!cls->initialize()) {        _class_initialize(cls);    }    Class curClass = cls;    IMP imp = nil;    do { //先查缓存,缓存没有时重建,仍旧没有则向父类查询        if (!curClass) break;        if (!curClass->cache) fill_cache(cls, curClass);        imp = cache_getImp(curClass, sel);        if (imp) break;    } while (curClass = curClass->superclass);    return imp;}虽然Apple没有公开_objc_msgForward的实现源码,但是我们还是能得出结论:
_objc_msgForward是一个函数指针(和 IMP 的类型一样),是用于消息转发的:当向一个对象发送一条消息,但它并没有实现的时候,_objc_msgForward会尝试做消息转发。
上篇中的《objc中向一个对象发送消息[obj foo]和objc_msgSend()函数之间有什么关系?》曾提到objc_msgSend在“消息传递”中的作用。在“消息传递”过程中,objc_msgSend的动作比较清晰:首先在 Class 中的缓存查找 IMP (没缓存则初始化缓存),如果没找到,则向父类的 Class 查找。如果一直查找到根类仍旧没有实现,则用_objc_msgForward函数指针代替 IMP 。最后,执行这个 IMP 。
为了展示消息转发的具体动作,这里尝试向一个对象发送一条错误的消息,并查看一下_objc_msgForward是如何进行转发的。
首先开启调试模式、打印出所有运行时发送的消息: 可以在代码里执行下面的方法:
(void)instrumentObjcMessageSends(YES);或者断点暂停程序运行,并在 gdb 中输入下面的命令:
call (void)instrumentObjcMessageSends(YES)以第二种为例,操作如下所示:
之后,运行时发送的所有消息都会打印到/tmp/msgSend-xxxx文件里了。
终端中输入命令前往:
open /private/tmp
可能看到有多条,找到最新生成的,双击打开
在模拟器上执行执行以下语句(这一套调试方案仅适用于模拟器,真机不可用,关于该调试方案的拓展链接: Can the messages sent to an object in Objective-C be monitored or printed out? ),向一个对象发送一条错误的消息:
////  main.m//  CYLObjcMsgForwardTest////  Created by http://weibo.com/luohanchenyilong/.//  Copyright (c) 2015年 微博@iOS程序犭袁. All rights reserved.//#import #import "AppDelegate.h"#import "CYLTest.h"int main(int argc, char * argv[]) {    @autoreleasepool {        CYLTest *test = [[CYLTest alloc] init];        [test performSelector:(@selector(iOS程序犭袁))];        return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));    }}
你可以在/tmp/msgSend-xxxx(我这一次是/tmp/msgSend-9805)文件里,看到打印出来:
+ CYLTest NSObject initialize+ CYLTest NSObject alloc- CYLTest NSObject init- CYLTest NSObject performSelector:+ CYLTest NSObject resolveInstanceMethod:+ CYLTest NSObject resolveInstanceMethod:- CYLTest NSObject forwardingTargetForSelector:- CYLTest NSObject forwardingTargetForSelector:- CYLTest NSObject methodSignatureForSelector:- CYLTest NSObject methodSignatureForSelector:- CYLTest NSObject class- CYLTest NSObject doesNotRecognizeSelector:- CYLTest NSObject doesNotRecognizeSelector:- CYLTest NSObject class结合《NSObject官方文档》,排除掉 NSObject 做的事,剩下的就是_objc_msgForward消息转发做的几件事:
  • 调用resolveInstanceMethod:方法 (或 resolveClassMethod:)。允许用户在此时为该 Class 动态添加实现。如果有实现了,则调用并返回YES,那么重新开始objc_msgSend流程。这一次对象会响应这个选择器,一般是因为它已经调用过class_addMethod。如果仍没实现,继续下面的动作。
  • 调用forwardingTargetForSelector:方法,尝试找到一个能响应该消息的对象。如果获取到,则直接把消息转发给它,返回非 nil 对象。否则返回 nil ,继续下面的动作。注意,这里不要返回 self ,否则会形成死循环。
  • 调用methodSignatureForSelector:方法,尝试获得一个方法签名。如果获取不到,则直接调用doesNotRecognizeSelector抛出异常。如果能获取,则返回非nil:创建一个 NSlnvocation 并传给forwardInvocation:。
  • 调用forwardInvocation:方法,将第3步获取到的方法签名包装成 Invocation 传入,如何处理就在这里面了,并返回非ni。
  • 调用doesNotRecognizeSelector: ,默认的实现是抛出异常。如果第3步没能获得一个方法签名,执行该步骤。

上面前4个方法均是模板方法,开发者可以override,由 runtime 来调用。最常见的实现消息转发:就是重写方法3和4,吞掉一个消息或者代理给其他对象都是没问题的
也就是说_objc_msgForward在进行消息转发的过程中会涉及以下这几个方法:
  • resolveInstanceMethod:方法 (或 resolveClassMethod:)。
  • forwardingTargetForSelector:方法
  • methodSignatureForSelector:方法
  • forwardInvocation:方法
  • doesNotRecognizeSelector: 方法

下面回答下第二个问题“直接_objc_msgForward调用它将会发生什么?”
直接调用_objc_msgForward是非常危险的事,如果用不好会直接导致程序Crash,但是如果用得好,能做很多非常酷的事。
就好像跑酷,干得好,叫“耍酷”,干不好就叫“作死”。
正如前文所说:
_objc_msgForward是 IMP 类型,用于消息转发的:当向一个对象发送一条消息,但它并没有实现的时候,_objc_msgForward会尝试做消息转发。
如何调用_objc_msgForward? _objc_msgForward隶属 C 语言,有三个参数 :





15 个回复

倒序浏览
存起来慢慢看...
回复 使用道具 举报
总结了很多啊,不错不错!赞一个
回复 使用道具 举报
先存一下 谢谢楼主了
回复 使用道具 举报
感觉好复杂啊
回复 使用道具 举报
这是工作面试题还是就业办面试题啊
回复 使用道具 举报
caizexu 中级黑马 2015-11-28 10:57:04
7#
感谢分享
回复 使用道具 举报
666666666666666666666
回复 使用道具 举报
我日了个去
回复 使用道具 举报
存下来,慢慢看
回复 使用道具 举报
这个必须得保存啊
回复 使用道具 举报
谢谢分享
回复 使用道具 举报
不错..................
回复 使用道具 举报
看上去很难 的样子
回复 使用道具 举报
好复杂!    辛苦了!
回复 使用道具 举报
存起来慢慢看
回复 使用道具 举报
您需要登录后才可以回帖 登录 | 加入黑马