OC的方法与消息
- 方法:与一个类相关的一段实际代码,并给出一个特定的名字。
- 消息:发送给对象的名称和一组参数。示例:向0x12345678对象发送meaning并且没有参数。
- 选择器:表示消息或方法名称的一种特殊方式,表示为类型SEL。选择器本质上就是不透明的字符串,它们被管理,因此可以使用简单的指针相等来比较它们,从而提高速度。(实现可能会有所不同,但这基本上是他们在外部看起来的样子。)例如:@selector(meaning)
- 消息发送:接收信息并查找和执行适当方法的过程。
方法与消息发送
消息在OC中方法调用是一个消息发送的过程。OC方法最终被生成为C函数,并带有一些额外的参数。这个C函数objc_msgSend就负责消息发送。
1 | OBJC_EXPORT id _Nullable |
可以看到传入的是一个objc_super结构体,结构如下:
1 | struct objc_super { |
receiver:消息接收者,是当前class
super_class :父类,从父类开始寻找方法,而略过当前类
所以 调用[super xxxx] 并不是说父类来调用方法,只是从父类开始寻找方法,然后发送到当前class,最终作用对象,还是当前class 。
消息发送的主要步骤
- 先在cache中查找imp,找到了返回imp
在当前class的method list中查找有无imp , 在class的方法列表methods中,根据SEL查找对应的imp
找到了 ,将imp存储到当前class 的cache中
在class的所有super classes中查找imp(先看Super class的cache,再看super class的方法列表)
找到了,同3
均未找到imp,进入动态方法解析流程resolveMethod
消息转发
没有方法的实现,程序会在运行时挂掉并抛出 unrecognized selector sent to … 的异常。但在异常抛出前,Objective-C 的运行时会给你三次拯救程序的机会:
- Method resolution
- Fast forwarding
- Normal forwarding
动态方法解析: Method Resolution
首先,Objective-C 运行时会调用以下方法,让你有机会提供一个函数实现。如果你添加了函数并返回 YES, 那运行时系统就会重新启动一次消息发送的过程。
1 | + (BOOL)resolveInstanceMethod:(SEL)sel {} (实例方法) |
可以以下实现:
1 | void eatMethod(id obj, SEL _cmd) |
我们只需要在 resolveInstanceMethod: 方法中,利用 class_addMethod 方法,将未实现的 eatMethod:绑定到 eatMethod 上就能完成转发,最后返回 YES。
这里第一字符v代表函数返回类型void,第二个字符@代表self的类型id,第三个字符:代表_cmd的类型SEL。官方地址
快速转发: Fast Rorwarding
1 | - (id)forwardingTargetForSelector:(SEL)aSelector {} |
消息转发机制执行前,runtime系统允许我们替换消息的接收者为其他对象。通过上面方法。如果此方法返回的是nil 或者self,则会进入消息转发机制(- (void)forwardInvocation:(NSInvocation *)invocation)
,否则将会向返回的对象重新发送消息。
相关实现
1 | - (id)forwardingTargetForSelector:(SEL)aSelector { |
完整消息转发: Normal Forwarding
1 | - (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {} |
第一个要求返回一个方法签名,第二个方法转发具体的实现。二者相互依赖,只有返回了正确的方法签名,才会执行第二个方法。
forwardInvocation:
方法就是一个不能识别消息的分发中心,将这些不能识别的消息转发给不同的消息对象,或者转发给同一个对象,再或者将消息翻译成另外的消息,亦或者简单的处理掉某些消息,因此没有响应也不会报错。例如:我们可以为了避免直接闪退,可以当消息没法处理时在这个方法中给用户一个提示,也不失为一种友好的用户体验。
其中,参数invocation
是从哪来的?在forwardInvocation:
消息发送前,runtime系统会向对象发送methodSignatureForSelector:
消息,并取到返回的方法签名用于生成NSInvocation
对象。所以重写forwardInvocation:
的同时也要重写methodSignatureForSelector:
方法,否则会抛出异常。当一个对象由于没有相应的方法实现而无法响应某个消息时,运行时系统将通过forwardInvocation:
消息通知该对象。每个对象都继承了forwardInvocation:
方法,我们可以将消息转发给其它的对象。
相关实现:
1 | - (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector { |
如果三次都没有处理,就会抛出异常
1 | unrecognized selector sent to instance 0x6d9f817072b1 |
模拟多继承
转发和继承相似,可用于为OC编程添加一些多继承的效果,一个对象把消息转发出去,就好像他把另一个对象中放法接过来或者“继承”一样。消息转发弥补了objc不支持多继承的性质,也避免了因为多继承导致单个类变得臃肿复杂。
相关代码实现:
1 |
|