最近看了一些文章,觉得很不错,因为这方面的文章关于细节部分的讲解比较多,重点推荐罗朝晖的两篇文章(将在本文附件中给出文章链接),所以就不再重复刷,就大致讲解一下消息响应的处理流程;
一般我们都是通过@selector(methodName)来向对象发送消息,流程是如何的呢?先来看看method结构体:
<span style="font-size:18px;">typedef struct objc_method *Method;
typedef struct objc_ method {
SEL method_name;
char *method_types;
IMP method_imp;
};</span>
能看出有三个变量:方法名,SEL和IMP。
其实系统通过@selector(methodName)将方法名methodName转化成一个SEL(这是一个Int类型),然后通过SEL去和方法列表中的方法进行匹配,匹配依据就是方法中的SEL变量,通过匹配可以得到方法中的IMP,从而找到了方法调用的入口地址(IMP),从而去执行方法。查找的路径是通过对象中的isa指针去找到对应的类,在类的方法缓存以及方法列表中查找,如果查找失败,就去所属父类的方法缓存和方法列表中查找,如果查找失败,再去父类的父类中查找。。。。
那么问题来了,如果一直找到根类都没有找到响应的方法呢?程序会crash?当然如果没有任何处理,确实会crash。
如何避免程序Crash?
如果程序没有找到能够响应的方法,此时我们实现了动态方法决议,程序就会自动进行方法决议,如果没有实现动态方法决议或者决议失败并且我们手动实现了消息转发机制,那么程序就会自动进行消息转发,否则程序还是会crash。也就是说如果同时提供了动态方法决议和消息转发机制,那么动态方法决议会先于消息转发,只有动态方法决议依然无法正确决议selector的实现时才会尝试进行消息转发。
所谓的动态方法决议其实就是在运行时动态地为一个selector提供实现,具体的做法是实现resolveInstaceMethod和resolveClassMethod方法并且为指定的selector提供实现。
#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运行时系统在抛出错误之前,会给消息接收对象发送一条特别的消息forwardInvocation 来通知该对象,该消息的唯一参数是个NSInvocation类型的对象——该对象封装了原始的消息和消息的参数。我们可以实现forwardInvocation:方法来对不能处理的消息做一些默认的处理,也可以将消息转发给其他对象来处理,而不抛出错误。
- (void) forwardInvocation:(NSInvocation *)anInvocation {
if ([someOtherObject respondsToSelector:[anInvocation selector]])
[anInvocation invokeWithTarget:someOtherObject];
else
[super forwardInvocation:anInvocation];
}
备注:resolveInstanceMethod、resolveClassMethod以及forwardInvocation方法都是NSObject类中的方法;
附件: