众所周知,oc是一门动态语言,也就意味着他需要一个运行时系统来动态的创建类和对象、进行消息传递和转发,而Runtime的机制就体现了他的动态性。
Runtime
的特性主要是消息传递,如果消息在对象中找不到,就进行转发。
Runtime的消息传递
1 | //对象 |
由上面的结构体可知消息传递的步骤:
- 系统首先找到消息的接收对象,然后通过对象的
isa
指针找到对象所属的类或者类所属的元类 - 先去类或者元类的cache列表中根据SEL去找这个方法
- 没有找到则去类中的
method_list
里查找,看是否有SEL方法 - 没有找到就去父类的
method_list
里查找 - 找到了,执行对应方法里的IMP指针,调用这个函数
- 没有找到,进入动态方法解析或者消息转发。
Runtime消息转发
上面说到了消息传递的过程,最后一步如果没找到对应方法,那么就会进入动态方法解析或者消息转发,而这正是系统留给我们的最后三次机会。
- 动态方法解析
- 消息重定向
- 消息转发
1.动态方法解析
如下代码:
1 | - (void)viewDidLoad { |
我们指定了clickHandle:方法,但我们没有定义,如果我们这时候不做任何处理,那么点击这个按钮必然崩溃。
添加如下代码:
1 | //#import "objc/runtime.h" |
因为消息转发的第一步走的是动态方法解析,在这里我们写了resolveInstanceMethod
方法 (如果是类方法调用则重写resolveClassMethod
方法),必然会走该方法,这时候通过runtime提供的添加方法,我们指定一个新方法的IMP,并定义这个IMP对应的函数(方法名一样即可),也就是将消息动态解析成了我们指定的一个方法。
打印如下:
1 | Doing something |
但如果我们没有写resolveInstanceMethod
方法呢,那么系统默认这个方法返回了NO,运行时就会移到下一步:forwardingTargetForSelector
2.消息转发一:消息重定向
在上面第一步消息动态方法解析注释掉或者返回NO,则消息转发进入了消息重定向。如下代码:
1 | - (id)forwardingTargetForSelector:(SEL)aSelector { |
定义一个新的类NewClass,并在类中实现ClickHandhle:方法
1 | - (void)clickHandhle:(UIView *)btn { |
打印结果如下:
1 | 这是新类里的方法 |
可以看到forwardingTargetForSelector
把当前类中的方法转发给了NewClass去执行了。
如果该方法返回ni或者self,则会进入最终的消息转发。
3.消息转发二:完整消息转发
如果在上一步还不能处理未知消息,则唯一能做的就是启用完整的消息转发机制了。首先他会通过methodSignatureForSelector
方法将未实现的方法的相关信息打包成一个NSInvocation
对象,然后调用forwardInvovation:
方法去交给一个类实现。当然了如果methodSignatureForSelector
返回nil,则Runtime会发出doesNotRecognizeSelector
消息,程序这时就会挂掉了。
如下代码:
1 | - (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector { |
打印结果:
1 | 这是新类里的方法 |
从打印结果来看,我们实现了完整的转发。通过签名,Runtime生成了一个NSInvocation
对象,发送给了forwardInvovation
,我们在forwardInvovation
方法里面让NewClass对象去执行了clickHandhle:函数。