查找 IMP 的过程

来源:互联网 发布:发展环境优化月报 编辑:程序博客网 时间:2024/06/16 00:20


一、消息函数obj_msgSend:

编译器会将消息转换为对消息函数 objc_msgSend的调用,该函数有两个主要的参数:消息接收者id 和消息对应的方法选标 SEL, 同时接收消息中的任意参数:

id objc_msgSend(id theReceiver, SELtheSelector, ...)

如 [aBird fly]会被转换为如下形式的函数调用:

objc_msgSend(aBird, @selector(fly));


二、查找 IMP 的过程:

1,首先去该类的方法 cache 中查找,如果找到了就返回它;

2,如果没有找到,就去该类的方法列表中查找。如果在该类的方法列表中找到了,则将 IMP 返回,并将它加入cache中缓存起来。根据最近使用原则,这个方法再次调用的可能性很大,缓存起来可以节省下次调用再次查找的开销。

3,如果在该类的方法列表中没找到对应的 IMP,在通过该类结构中的 super_class指针在其父类结构的方法列表中去查找,直到在某个父类的方法列表中找到对应的IMP,返回它,并加入cache中;

4,如果在自身以及所有父类的方法列表中都没有找到对应的 IMP,则看是不是可以进行动态方法决议;

5,如果动态方法决议没能解决问题,进入下面要讲的消息转发流程。


当给一个对象发送它不能处理的消息的时候,就需要使用下面的两个方案了。

三、动态方法决议

Objective C 提供了一种名为动态方法决议的手段,使得我们可以在运行时动态地为一个 selector 提供实现。我们只要实现 +resolveInstanceMethod: 和/或 +resolveClassMethod: 方法,并在其中为指定的 selector  提供实现即可(通过调用运行时函数 class_addMethod 来添加)。这两个方法都是 NSObject 中的类方法,其原型为:

+(BOOL)resolveClassMethod:(SEL)sel+(BOOL)resolveInstanceMethod:(SEL)sel
resolveInstanceMethod 是为对象方法进行决议,而 resolveClassMethod 是为类方法进行决议。


参数 sel 是需要被动态决议的 selector;返回值文档中说是表示动态决议成功与否。如果在函数内为指定的 selector  提供实现,无论返回 YES 还是 NO,编译运行都是正确的;但如果在该函数内并不真正为 selector 提供实现,无论返回 YES 还是 NO,运行都会 crash,道理很简单,selector 并没有对应的实现,而又没有实现消息转发。


只有在 resolveInstanceMethod 的实现中没有真正为 selector 提供实现,并返回 NO 的情况下才会进入消息转发流程;否则绝不会进入消息转发流程,程序要么调用正确的动态方法,要么 crash。


如果向一个 Objective C 对象对象发送它无法处理的消息(selector),那么编译器会按照如下次序进行处理:

1,首先看是否为该 selector 提供了动态方法决议机制,如果提供了则转到 2;如果没有提供则转到 3;

2,如果动态方法决议真正为该 selector 提供了实现,那么就调用该实现,完成消息发送流程,消息转发就不会进行了;如果没有提供,则转到 3;

3,其次看是否为该 selector 提供了消息转发机制,如果提供了消息了则进行消息转发,此时,无论消息转发是怎样实现的,程序均不会 crash。(因为消息调用的控制权完全交给消息转发机制处理,即使消息转发并没有做任何事情,运行也不会有错误,编译器更不会有错误提示。);如果没提供消息转发机制,则转到 4;

4,运行报错:无法识别的 selector,程序 crash;




四、消息转发

通常,给一个对象发送它不能处理的消息会得到出错提示,然而,Objective-C运行时系统在抛出错误之前,会给消息接收对象发送一条特别的消息forwardInvocation 来通知该对象,该消息的唯一参数是个NSInvocation类型的对象——该对象封装了原始的消息和消息的参数。

我们可以实现forwardInvocation:方法来对不能处理的消息做一些默认的处理,也可以将消息转发给其他对象来处理,而不抛出错误。


forwardInvocation:消息给这个问题提供了一个更特别的,动态的解决方案:当一个对象由于没有相应的方法实现而无法响应某消息时,运行时系统将通过forwardInvocation:消息通知该对象。每个对象都从NSObject类中继承了forwardInvocation:方法。然而,NSObject中的方法实现只是简单地调用了doesNotRecognizeSelector:。通过实现我们自己的forwardInvocation:方法,我们可以在该方法实现中将消息转发给其它对象。

要转发消息给其它对象,forwardInvocation:方法所必须做的有:
1,决定将消息转发给谁,并且
2,将消息和原来的参数一块转发出去。

#pragma mark--消息转发-(void)forwardInvocation:(NSInvocation *)anInvocation{    SEL name = [anInvocation selector];    NSLog(@" >> forwardInvocation %@",NSStringFromSelector(name));        Proxy *proxy =[[Proxy alloc]init];    if ([proxy respondsToSelector:name]) {        [anInvocation invokeWithTarget:proxy];    }    else{        [super forwardInvocation:anInvocation];    }}-(NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{    return [Proxy instanceMethodSignatureForSelector:aSelector];}

forwardInvocation:方法就像一个不能识别的消息的分发中心,将这些消息转发给不同接收对象。或者它也可以象一个运输站将所有的消息都发送给同一个接收对象。它可以将一个消息翻译成另外一个消息,或者简单的"吃掉“某些消息,因此没有响应也没有错误。forwardInvocation:方法也可以对不同的消息提供同样的响应,这一切都取决于方法的具体实现。该方法所提供是将不同的对象链接到消息链的能力。

注意: forwardInvocation:方法只有在消息接收对象中无法正常响应消息时才会被调用。 所以,如果我们希望一个对象将negotiate消息转发给其它对象,则这个对象不能有negotiate方法,也不能在动态方法决议中为之提供实现。否则,forwardInvocation:将不可能会被调用。

五、关于动态决议和消息转发同时出现的话:

//在发送一个无法识别的消息的时候,动态方法决议是先于消息转发的。//如果向一个 Objective C 对象对象发送它无法处理的消息(selector),那么编译器会按照如下次序进行处理:/** *1、首先看是否为该 selector 提供了动态方法决议机制,如果提供了则转到2;如果没有提供则转到3 *2、如果动态方法决议真正为该 selector 提供了实现,那么就调用该实现,完成消息发送流程,消息转发就不会进行了;如果没有提供,则转到 3; *3、其次看是否为该 selector 提供了消息转发机制,如果提供了消息了则进行消息转发,此时,无论消息转发是怎样实现的,程序均不会 crash。(因为消息调用的控制权完全交给消息转发机制处理,即使消息转发并没有做任何事情,运行也不会有错误,编译器更不会有错误提示。);如果没提供消息转发机制,则转到 4; *4、运行报错:无法识别的 selector,程序 crash; */



参考资料:

http://www.cppblog.com/kesalin/archive/2011/08/15/objc_message.html

http://www.cppblog.com/kesalin/archive/2012/11/14/dynamic_method_resolve.html

0 0
原创粉丝点击