方法调用的本质是消息发送。
底层执行了objc_msgSend函数,默认且必要参数是方法的执行者和方法的标示个。如果该方法有其他参数则添加在这两个参数后面。
方法调用前要进行方法查找,方法查找流程分为快速方法查找和慢速方法查找两部分。
快速方法查找会先在类的方法缓存中查找是否有目标方法,有则执行没有则进入方法列表查找。
如果在方法列表中找到了该方法会直接执行。找不到则开启慢速方法查找流程lookUpImpOrForward。慢速方法查找会逐级向上,往父类的方法缓存和方法列表里查找该方法。找到了返回该方法实现IMP,找不到的话会进入消息转发流程。
消息转发
方案A:Method resolution-方法解析,解决
注意:添加方法时:class_addMethod中,实例方法是[self class],类方法是objc_getMetaClass(“ViewController”), 因为类方法最终查找方法实现是去对象的元类对象中查找,所以添加也是去元类对象中添加
- (void)viewDidLoad {
[super viewDidLoad];
//类方法
[ViewController performSelector:@selector(noMethod)];
//实例方法
[self performSelector:@selector(noMethod)];
}
- (void)test {
NSLog(@"拦截到 %@ crash",NSStringFromSelector(_cmd));
}
//实例方法
+ (BOOL)resolveInstanceMethod:(SEL)sel {
if ([NSStringFromSelector(sel) isEqualToString:@"noMethod"]) {
IMP imp = class_getMethodImplementation([self class], @selector(test));
class_addMethod([self class], sel, imp, "v@");
}
return [super resolveInstanceMethod:sel];
}
//类方法
+ (BOOL)resolveClassMethod:(SEL)sel {
if ([NSStringFromSelector(sel) isEqualToString:@"noMethod"]) {
IMP imp = class_getMethodImplementation([ViewController class], @selector(test));
class_addMethod(objc_getMetaClass("ViewController"), sel, imp, "v@");
}
return [super resolveClassMethod:sel];
}
方案B:Fast forwarding-消息快速转发
这一步比较简单,就是找一个能实现该方法的对象,让这个对象去实现该方法;
- (id)forwardingTargetForSelector:(SEL)aSelector {
if ([[Person new] respondsToSelector:aSelector]) {
return [Person new];
}
return [super forwardingTargetForSelector:aSelector];
}
方案C:Forwarding-消息慢转发
当然这里的慢是相对于方案B的,不是说真的就是很慢。
这里的 “v@:@”就代表:
- “v”:代表返回值void
- “@”:代表一个对象,也就是消息的receiver
- “:”:代表SEL
- “@”:代表参数(字符串类型)
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
if ([super methodSignatureForSelector:aSelector] == nil) {
NSMethodSignature *methodSign = [NSMethodSignature signatureWithObjCTypes:"v@:"];
return methodSign;
} else {
return [super methodSignatureForSelector:aSelector];
}
}
- (void)forwardInvocation:(NSInvocation *)anInvocation {
SEL sel = anInvocation.selector;
if ([[Person new] respondsToSelector:sel]) {
[anInvocation invokeWithTarget:[Person new]];
} else {
[super forwardInvocation:anInvocation];
}
}
注意:以上三个步骤,效率上 A > B > C,由于对象会缓存方法,如果方案A实现了,addMethod,那么下次调用该方法时,会直接从对象的method cache 里面直接调用,效率更高。A,B,C都实现了,只会执行A!