oc 是一门动态语言,在编译的过程中并不能确定变量的所属类型,也不能确定真正调用哪个函数。只有在运行时才能确定。实现 oc 运行时机制的基础就是 runtime
方法调用的三个步骤:
1、在编译时转化为 C 函数 obj_msgSend(receicer, selector)。先在本类的cache中查找方法,找不到去该类的objc_method_list列表中去查找,找不到会递归去父类中查找。
2、父类中也没有找到,会进行动态方法解析,是否动态为该类添加了该方法。
3、没有动态添加,会进行消息转发,崩溃前的补救措施
消息转发
消息转发机制分为3个步骤
1、Method resolution 方法解析
在调用无法解读(没有实现)的消息后,首先会调用对象方法 resolveInstanceMethod: 或者类方法 resolveClassMethod: 询问是否有动态添加方法来进行处理, 如果返回 YES 则能接受,返回 NO 进入第二步。
新建一个Dog类,.h中暴露run方法,.m中不去实现。
@interface Dog : NSObject
- (void)run;
@end
当我们调用run方式时
Dog *dog = [Dog new];
[dog run];
会发生崩溃
reason: '-[Dog run]: unrecognized selector sent to instance 0x6000031d5940'
这时候我们导入<objc/runtime.h>,重写 resolveInstanceMethod: 动态添加该方法,运行不会发生崩溃,并打印resolveInstanceMethod----------add run Method
返回YES时,会继续去方法列表中查找,没找到的话继续进入消息转发,如果返回YES,但没有动态添加该方法,会导致不断的循环
+ (BOOL)resolveInstanceMethod:(SEL)sel {
if (sel == @selector(run)) {
class_addMethod([self class], sel, imp_implementationWithBlock(^(id self){
NSLog(@"resolveInstanceMethod----------add run Method");
}), "v@:");
return YES;
}
return [super resolveInstanceMethod:sel];
}
v-void,@-id,: - _cmd的类型SEL,官方文档
2、Fast forwarding 快速转发
调用 forwardingTargetForSelector: 方法,是否有备用接受者,让实现该方法的其他类去处理;如果没有备用接受者,执行第三步。
另创建Person类,实现run方法。
- (void)run {
NSLog(@"Person------run");
}
重写 forwardingTargetForSelector: 方法,并返回 Person 对象,运行程序,打印 Person------run
- (id)forwardingTargetForSelector:(SEL)aSelector {
if (aSelector == @selector(run)) {
return [Person new];
}
return nil;
}
3、Normal forwarding常规转发
先调用 methodSignatureForSelector: 返回SEL方法的签名
再调用 forwardInvocation: 进入消息转发最后一步,创建备用对象,判断备用对象是否可以响应SEL,如果不能响应,则抛出异常。
方法签名
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
{
NSMethodSignature *m = [super methodSignatureForSelector:aSelector];
if (!m) {
m = [NSMethodSignature signatureWithObjCTypes:"v@:"];
}
return m;
}
将不能识别的消息转发给其他对象,可连续转发给多个对象,运行输出 Person------run
- (void)forwardInvocation:(NSInvocation *)anInvocation {
Person *p = [Person new];
if([p respondsToSelector:anInvocation.selector]) {
[anInvocation invokeWithTarget:p];
}else {
[self doesNotRecognizeSelector:anInvocation.selector];
}
}
实战
1、防止未实现方法而崩溃
#import "NSObject+CrashHandle.h"
#import <AppKit/AppKit.h>
@implementation NSObject (CrashHandle)
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
return [NSMethodSignature signatureWithObjCTypes:"v@:@"];
}
- (void)forwardInvocation:(NSInvocation *)anInvocation {
NSLog(@"未实现该方法---%@", NSStringFromSelector(anInvocation.selector));
}
2、模拟多继承
3、苹果系统API迭代造成API不兼容的奔溃处理
Method Swizzing 黑魔法
Method Swizzing 是发生在运行时的,可以调用 class_replaceMethod()、method_exchangeImplementations 将两个 Method 进行交换
可以解决防数组越界,防止短时间内多次点击事件(button点击事件交换系统方法sendAction:to:forEvent:)、埋点等问题
参考:https://www.jianshu.com/p/9263720cbd91
参考:https://juejin.im/post/5ae96e8c6fb9a07ac85a3860