黑马程序员技术交流社区

标题: 面试题总结下 [打印本页]

作者: xqlyn123    时间: 2015-11-27 23:13
标题: 面试题总结下
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消息转发做的几件事:
上面前4个方法均是模板方法,开发者可以override,由 runtime 来调用。最常见的实现消息转发:就是重写方法3和4,吞掉一个消息或者代理给其他对象都是没问题的
也就是说_objc_msgForward在进行消息转发的过程中会涉及以下这几个方法:
下面回答下第二个问题“直接_objc_msgForward调用它将会发生什么?”
直接调用_objc_msgForward是非常危险的事,如果用不好会直接导致程序Crash,但是如果用得好,能做很多非常酷的事。
就好像跑酷,干得好,叫“耍酷”,干不好就叫“作死”。
正如前文所说:
_objc_msgForward是 IMP 类型,用于消息转发的:当向一个对象发送一条消息,但它并没有实现的时候,_objc_msgForward会尝试做消息转发。
如何调用_objc_msgForward? _objc_msgForward隶属 C 语言,有三个参数 :






作者: Mr.Yan    时间: 2015-11-27 23:30
存起来慢慢看...
作者: 罗文强    时间: 2015-11-28 00:27
总结了很多啊,不错不错!赞一个
作者: 沐小妖mavs    时间: 2015-11-28 00:40
先存一下 谢谢楼主了
作者: cbl16888    时间: 2015-11-28 07:18
感觉好复杂啊
作者: lujia1010    时间: 2015-11-28 10:26
这是工作面试题还是就业办面试题啊
作者: caizexu    时间: 2015-11-28 10:57
感谢分享
作者: FengLinHuoShan    时间: 2015-11-28 11:10
666666666666666666666
作者: xiaoniu706    时间: 2015-11-28 12:18
我日了个去
作者: cube川    时间: 2015-11-28 17:05
存下来,慢慢看
作者: 一枚小刁民    时间: 2015-11-28 17:20
这个必须得保存啊
作者: paul20150103    时间: 2015-11-28 18:01
谢谢分享
作者: 马尔代夫的日出    时间: 2015-11-28 22:13
不错..................
作者: cherrycool    时间: 2015-11-28 22:20
看上去很难 的样子
作者: 学习黑马精神    时间: 2015-11-28 22:30
好复杂!    辛苦了!
作者: 斥候7300    时间: 2015-11-28 22:51
存起来慢慢看




欢迎光临 黑马程序员技术交流社区 (http://bbs.itheima.com/) 黑马程序员IT技术论坛 X3.2