如果我们在 ObjectiveC 中向一个对象发送它无法处理的消息,会出现什么情况呢?我们知道发送消息是通过 objc_send(id, SEL, ...) 来实现的,它会首 先在对象的类对象的 cache,methodlist 以及父类对象的 cache,methodlist 中依次查找 SEL 对应 的 IMP;如果没有找到且实现了动态方法决议机制就会进行决议,如果没有实现动态方法决议机制或决议 失败且实现了消息转发机制就会进入消息转发流程,否则程序 crash。也就是说如果同时提供了动态方法 决议和消息转发,那么动态方法决议先于消息转发,只有当动态方法决议依然无法正确决议 selector 的 实现,才会尝试进行消息转发。在前文中,我并没有详细讲解动态方法决议,因此本文将详细介绍之。
动态方法决议
我们先定义一个类:
Objective C 提供了一种名为动态方法决议的手段,使得我们可以在运行时动态地为一个 selector 提供 实现。我们只要实现 +resolveInstanceMethod: 和/或 +resolveClassMethod: 方法,并在其中为指 定的 selector 提供实现即可(通过调用运行时函数 class_addMethod 来添加)。这两个方法都是 NSObject 中的类方法,其原型为:
- (BOOL)resolveClassMethod:(SEL)name; + (BOOL)resolveInstanceMethod:(SEL)name;
参数 name 是需要被动态决议的 selector ,返回值文档中说是表示动态决议成功与否。但在上面的例子 中(不涉及消息转发的情况下),如果在该函数内为指定的 selector 提供实现,无论返回 YES 还是 NO, 编译运行都是正确的;但如果在该函数内并不真正为 selector 提供实现,无论返回 YES 还是 NO,运 行都会 crash,道理很简单,selector 并没有对应的实现,而又没有实现消息转发。 resolveInstanceMethod 是为对象方法进行决议,而 resolveClassMethod 是为类方法进行决议。
@interface Foo : NSObject
-(void)MissMethod;
- (void)Bar;
@end
#import "Foo.h"
void dynamicMethodIMP(id self, SEL _cmd) {
NSLog(@" >> dynamicMethodIMP");
}
@implementation Foo
+ (BOOL)resolveInstanceMethod:(SEL)name {
NSLog(@" >> Instance resolving %@", NSStringFromSelector(name));
if (name == @selector(MissMethod)) {
class_addMethod([self class], name, (IMP)dynamicMethodIMP, "v@:"); return YES;
}
return [super resolveInstanceMethod:name];
}
+ (BOOL)resolveClassMethod:(SEL)name {
NSLog(@" >> Class resolving %@", NSStringFromSelector(name));
return [super resolveClassMethod:name];
}
- (void)Bar {
NSLog(@" >> Bar() in Foo");
}
//- (void)MissMethod {
//
//}
@end
然后调用
#import "ViewController.h"
#import "Foo.h"
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
Foo *foo = [[Foo alloc] init];
[foo Bar];
[foo MissMethod];
}
打印的信息:
**2017-07-02 15:58:53.578584+0800 DynamicMethodDemo[3147:601367] >> Bar() in Foo**
**2017-07-02 15:58:53.578782+0800 DynamicMethodDemo[3147:601367] >> Instance resolving MissMethod**
**2017-07-02 15:58:53.579087+0800 DynamicMethodDemo[3147:601367] >> dynamicMethodIMP**
如果将 [图片上传失败...(image-f485ae-1522657310056)]
//class_addMethod([self class], name, (IMP)dynamicMethod IMP, "v@:");
这行注释掉,虽然会返回YES,但是还是会崩溃,在这里,resolveInstanceMethod 使诈了,它声称成功(返回 YES)决议了 selector,但是并没有真正 提供实现,被编译器发觉而提示相应的错误信息。那它的返回值到底有什么作用呢,在它没有提供真正的 实现,并且提供了消息转发机制的情况下,YES 表示不进行后续的消息转发,返回 NO 则表示要进行后 续的消息转发。
动态方法的内部实现
1,首先判断是否实现了 resolveInstanceMethod,如果没有实现,返回 NULL,进入下一步处理; 2,如果实现了,调用 resolveInstanceMethod,获取返回值;
3,如果返回值为 YES,表示 resolveInstanceMethod 声称它已经提供了 selector 的实现,因此再次 查找 methodlist,如果依然找到对应的 IMP,则返回该实现,否则提示警告信息,返回 NULL,进入下 一步处理;
4,如果返回值为 NO,返回 NULL,进入下一步处理;
加入消息转发内部走 _objc_msgForward
- (void)forwardInvocation:(NSInvocation *)anInvocation {
SEL name = [anInvocation selector];
NSLog(@" >> forwardInvocation for selector %@", NSStringFromSelector(name));
Proxy * proxy = [[Proxy alloc] init];
if ([proxy respondsToSelector:name]) {
[anInvocation invokeWithTarget:proxy];
}
else {
[super forwardInvocation:anInvocation];
}
}
//methodSignatureForSelector:的作用在于为另一个类实现的消息创建一个有效的方法签名,必须实现,并且返回不为空的methodSignature,否则会crash。
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector { return [Proxy instanceMethodSignatureForSelector:aSelector];
}
总结:
从上面的示例演示可以看出,动态方法决议是先于消息转发的。
如果向一个 Objective C 对象对象发送它无法处理的消息(selector),那么编译器会按照如下次序进 行处理:
1,首先看是否为该 selector 提供了动态方法决议机制,如果提供了则转到 2;如果没有提供则转到 3; 2,如果动态方法决议真正为该 selector 提供了实现,那么就调用该实现,完成消息发送流程,消息转发
就不会进行了;如果没有提供,则转到 3;
3,其次看是否为该 selector 提供了消息转发机制,如果提供了消息了则进行消息转发,此时,无论消息 转发是怎样实现的,程序均不会 crash。(因为消息调用的控制权完全交给消息转发机制处理,即使消息
转发并没有做任何事情,运行也不会有错误,编译器更不会有错误提示。);如果没提供消息转发机制, 则转到 4;
4,运行报错:无法识别的 selector,程序 crash;
则到现在为止,我们已经了解了消息的全部原理,在这里再整理总结一遍
1.当实例对象调用方法的时候,将方法转换成 id objc_msgSend(id theReceiver, SELtheSelector, ...) (该消息做了动态绑定的所需要的一切工作)
2.首先根据SEL去该类的方法 cache 中查找,如果找到了调到6;
3.如果没有找到,就去该类的方法列表中查找。如果在该类的方法列表中找到了,跳到6,并将 它加入 cache 中缓存起来。根据最近使用原则,这个方法再次调用的可能性很大,缓存起来可以节省下次 调用再次查找的开销。
4.如果在该类的方法列表中没找到对应的 IMP,在通过该类结构中的 super_class 指针在其父类结构的方法列表中去查找,直到在某个父类的方法列表中找到对应的 IMP,跳转到6,并加入 cache 中;
5.如果在自身以及所有父类的方法列表中都没有找到对应的 IMP,首先看是否为该 selector 提供了动态方法决议机制,如果动态方法决议真正为该 selector 提供了实现,那么就调用该实现,完成消息发送流程,消息转发 ,其次看是否为该 selector 提供了消息转发机制,如果提供了消息了则进行消息转发,此时,无论消息 转发是怎样实现的,程序均不会 crash。(因为消息调用的控制权完全交给消息转发机制处理,即使消息 转发并没有做任何事情,运行也不会有错误,编译器更不会有错误提示
6.它首先找到 SEL 对应的方法实现 IMP。因为不同的类对同一方法可能会有不同的实现,所以找到的 方法实现依赖于消息接收者的类型。
7.然后将消息接收者对象(指向消息接收者对象的指针)以及方法中指定的参数传递给方法实现 IMP。
8.最后,将方法实现的返回值作为该函数的返回值返回。