Runtime 是 Objective-C 区别于 C 语言这样的静态语言的一个非常重要的特性。对于 C 语言,函数的调用会在编译期就已经决定好,在编译完成后直接顺序执行。但是 OC 是一门动态语言,函数调用变成了消息发送,在编译期不能知道要调用哪个函数。所以 Runtime 无非就是去解决如何在运行时期找到调用方法这样的问题。
消息发送
消息发送
消息转发
当向一个对象发送一条消息,但它并没有实现的时候,_objc_msgForward
会尝试做消息转发
简单打印下消息转发的log(Xcode 10及以上未出现打印)
首先随便创建个类(RuntimeTest
继承与NSObject
,未实现test
方法),编写一下代码
RuntimeTest *test = [[RuntimeTest alloc] init];
[test performSelector:@selector(test)];
开启debug模式,在下面这句加断点,设备选择模拟器
RuntimeTest *test = [[RuntimeTest alloc] init];
运行代码,当执行到断点暂停时在lldb中输入
po (void)instrumentObjcMessageSends(YES)
继续执行代码直至崩溃,使用终端 open /private/tmp/ 打开文件夹找到msgSends-xxxx(最新时间生成)文件,(文件部分内容)
- NSObject NSObject performSelector:
+ NSObject NSObject resolveInstanceMethod:
+ NSObject NSObject resolveInstanceMethod:
- NSObject NSObject forwardingTargetForSelector:
- NSObject NSObject forwardingTargetForSelector:
- NSObject NSObject methodSignatureForSelector:
- NSObject NSObject methodSignatureForSelector:
- NSObject NSObject class
- NSObject NSObject doesNotRecognizeSelector:
- NSObject NSObject doesNotRecognizeSelector:
- NSObject NSObject class
结合NSObject文档,可以知道_objc_msgForward
消息转发做了如下几件事:
通过
resolveInstanceMethod
得知方法是否为动态添加,YES则通过class_addMethod
动态添加方法,处理消息,否则进入下一步。dynamic
属性就与这个过程有关,当一个属性声明为dynamic
时 就是告诉编译器:开发者一定会添加setter/getter
的实现,而编译时不用自动生成。这步会进入
forwardingTargetForSelector
用于指定哪个对象来响应消息。如果返回nil
则进入第三步。这种方式把消息原封不动地转发给目标对象,有着比较高的效率。如果不能自己的类里面找到替代方法,可以重载这个方法,然后把消息转给其他的对象。这步调用
methodSignatureForSelector
进行方法签名,这可以将函数的参数类型和返回值封装。如果返回nil
说明消息无法处理并报错unrecognized selector sent to instance
,如果返回methodSignature
,则进入forwardInvocation
,在这里可以修改实现方法,修改响应对象等,如果方法调用成功,则结束。如果依然不能正确响应消息,则报错unrecognized selector sent to instance
.
使用场景:
- 可以在消息转发过程中,利用第3步
forwardInvocation
使用try
进行异常捕捉不抛出,从而避免一些崩溃 - 可以利用 2、3 中的步骤实现对接受消息对象的转移,可以实现“多重继承”的效果。
- 等等...