如果我们在Objective-C中,向一个对象发送无法它无法处理的消息,会出现什么情况?
我们知道发送消息是通过,objc_msgSend(id, SEL, ...)来实现的。首先会在对象的类对象的cache,method list以及父类对象的cache,method list依次查找SEL对应的IMP。
如果没有找到,并且实现了动态方法决议机制就会决议。如果没有实现动态决议机制或者决议失败且实现了消息转发机制。就会进入消息转发流程。否则程序Crash.
也就是说如果同时实现了动态决议和消息转发。那么动态决议先于消息转发。只有当动态决议无法决议selector的实现,才会尝试进行消息转发。
一,向一个对象发送该对象无法处理的消息
@interface Foo : NSObject
-(void)Bar;
@end
@implementation Foo
-(void)Bar
{
NSLog(@" >> Bar() in Foo");
}
@end
/////////////////////////////////////////////////
#import "Foo.h"
int main (int argc, const char * argv[])
{
@autoreleasepool {
Foo * foo = [[Foo alloc] init];
[foo Bar];
[foo MissMethod];
[foo release];
}
return 0;
}
-[Foo MissMethod]: unrecognized selector sent to instance 0x10010c840
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[Foo MissMethod]: unrecognized selector sent to instance 0x10010c840'
*** Call stack at first throw:
......
terminate called after throwing an instance of 'NSException'
对象无法处理 MissMethod 对应的 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 是为类方法进行决议。
下面我们用动态方法决议手段来修改上面的代码:
#import "Foo.h"
#include <objc/runtime.h>
void dynamicMethodIMP(id self, SEL _cmd) {
NSLog(@" >> dynamicMethodIMP");
}
@implementation Foo
-(void)Bar
{
NSLog(@" >> Bar() in 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];
}
@end
其实,objective-c的方法就是至少带有两个参数(self和cmd)的普通的C函数。
因此在上面的代码中提供这样一个 C 函数 dynamicMethodIMP,让它来充当对象方法 MissMethod 这个 selector 的动态实现
因为 MissMethod 是被对象所调用,所以它被认为是一个对象方法,因而应该在 resolveInstanceMethod 方法中为其提供实现。
通过调用
class_addMethod([self class], name, (IMP)dynamicMethodIMP, "v@:");
就能在运行期动态地为 name 这个 selector 添加实现:dynamicMethodIMP。class_addMethod 是运行时函数,所以需要导入头文件:objc/runtime.h。