一、简介
OC 是一门动态的语言,而 Runtime
是使用 OC 开发 iOS 应用的一个核心技术。OC 中很多动态的特性都是通过 Runtime
来实现的。
当一个类进行方法的调用时,本质是利用 Runtime
的消息机制发送一条消息,从而达到调用方法的目的,其底层就是调用了 objc_msgSend
函数。
想要发送一条消息给某个对象,最多会经历三个阶段,即消息发送,动态方法解析和消息转发。如果经历这三个阶段都没有找到该消息,那么程序在运行时就会抛出一个错误 :unrecognized selector sent to instance
。
二、消息发送
-
当我们给某个对象发送消息时,通过该对象的
isa
找到该对象的类对象。在类对象中的方法缓存中查找有没有该方法,如果有即进行调用。 -
如果在类对象的缓存中没有该方法,那就在该类对象存储方法的位置查找方法,如果有该方法,先将该方法加入类对象的方法缓存中去,然后调用该方法。
-
如果没有找到该方法,通过
superclass
指针找其父类,先查找父类的方法缓存中有没有该方法,如果有该方法那么就进行调用,如果缓存中没有该方法,那么就从父类的方法列表中查找方法,如果找到该方法,先缓存到类对象的方法缓存中,再进行调用 -
如果元类对象的父类还是没找到该方法,就找父类的父类依次类推,重复步骤3。如果最终都没有找到该方法就进行第二个阶段即动态方法解析。
三、动态方法解析
当消息发送阶段完成后仍然没有找到对应的方法,那么就会来到动态方法解析阶段。动态方法解析可以帮助我们拦截到消息发送阶段未实现的方法。在动态方法解析提供的方法中,利用 runtime
动态的添加某个方法。当消息发送阶段调用的方法没有找到的时候,来调用我们动态添加的方法。
下面以实例方法为例进行说明:
我们定义一个类,该类中只有方法的声明而没有实现。
@interface SomeClass : NSObject
- (void)foo;
@end
复制代码
因为是实例方法,所以我们在 SomeClass 的实现文件中需要重写 resolveInstanceMethod 方法,在该方法中来实现动态方法解析。
假设当我们调用 foo 方法时候,因为 foo 方法未被实现,来到动态方法解析阶段,我们让其调用 bar 方法的实现。
- (void)bar {
NSLog(@"%s", __func__);
}
+ (BOOL)resolveInstanceMethod:(SEL)sel {
// 判断方法名是否为 foo
if (sel == @selector(foo)) {
// 获取被添加的方法
Method method = class_getInstanceMethod(self, @selector(bar));
// 获取方法实现
// IMP imp = class_getMethodImplementation(self, @selector(bar));
IMP imp = method_getImplementation(method);
// 获取 TypeEncoding
const char *types = method_getTypeEncoding(method);
// 动态添加方法
class_addMethod(self, sel, imp, types);
return YES;
}
return [super resolveInstanceMethod:sel];
}
复制代码
- 利用
runtime
的class_addMetho
d 函数动态的添加方法- 第1个参数:如果是添加实例方法,传入类对象,如果是添加类方法传入元类对象
- 第2个参数:被动态添加方法的方法名称
- 第3个参数:动态添加方法的方法实现也就是函数地址
- 第4个参数:动态添加的方法的参数和返回值的
typeEncoding
动态解析阶段添加完方法后,会重新执行一遍消息发送的流程。
如果没有实现动态方法解析,那么就会来到第三个阶段消息转发阶段
四、消息转发
当前2个阶段都没有找到对应的消息,最终就会来到消息转发阶段,我们还是以实例方法进行举例说明:
- 首先会调用
- (id)forwardingTargetForSelector:(SEL)aSelector
方法,在该方法中进行消息的转发,将该消息转发到其他类,让其他类处理该消息。
- (id)forwardingTargetForSelector:(SEL)aSelector {
if (aSelector == @selector(foo)) {
return [[OtherClass alloc] init];
}
return [super forwardingTargetForSelector:aSelector];
}
// OtherClass
@interface OtherClass : NSObject
- (void)foo;
@end
@implementation OtherClass
- (void)foo {
NSLog(@"%s", __func__);
}
@end
复制代码
- 创建一个
OtherClass
的类,该类中声明并实现了foo
方法 forwardingTargetForSelector
方法中返回了OtherClass
的实例对象,意味着我们会将消息交给OtherClass
去处理,此时就会调用该类中的foo
方法。
如果 forwardingTargetForSelector
方法返回 nil
或者压根就没有实现,消息转发阶段会调用其他方法来进行处理。
- 首先调用的是
methodSignatureForSelector
方法,该方法返回一个NSMethodSignature
对象,如果返回值不为nil
,那么他将会调用forwardInvocation
方法,并且传入一个NSInvocation
对象,该对象封装了方法调用所必须的条件。
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
if (aSelector == @selector(foo)) {
Method method = class_getInstanceMethod([OtherClass class], aSelector);
const char *types = method_getTypeEncoding(method);
NSMethodSignature *signature = [NSMethodSignature signatureWithObjCTypes:types];
return signature;
}
return [super methodSignatureForSelector:aSelector];
}
- (void)forwardInvocation:(NSInvocation *)anInvocation {
[anInvocation invokeWithTarget:[[OtherClass alloc] init]];
// 假如 types 随便传的,anInvocation 封装的内容就毫无意义
// 我们可以随意进行任何操作
OtherClass *oc = [[OtherClass alloc] init];
[oc bar];
}
复制代码
- 如果我们想要用
anInvocation
来处理消息,那么在methodSignatureForSelector
方法中types
一定要传对,关于types
如何表示的我们可以查看苹果的官方文档Type Encoding。 - 如果我们不想利用
anInvocation
处理消息,types
其实就可以随便传,那么我们就不能使用anInvocation
来处理消息了,因为它封装的也是乱七八糟的东西。