iOS 消息转发篇(Message Forwarding) (消息机制)

OC的方法与消息

  • 方法:与一个类相关的一段实际代码,并给出一个特定的名字。
  • 消息:发送给对象的名称和一组参数。示例:向0x12345678对象发送meaning并且没有参数。
  • 选择器:表示消息或方法名称的一种特殊方式,表示为类型SEL。选择器本质上就是不透明的字符串,它们被管理,因此可以使用简单的指针相等来比较它们,从而提高速度。(实现可能会有所不同,但这基本上是他们在外部看起来的样子。)例如:@selector(meaning)
  • 消息发送:接收信息并查找和执行适当方法的过程。

方法与消息发送

消息在OC中方法调用是一个消息发送的过程。OC方法最终被生成为C函数,并带有一些额外的参数。这个C函数objc_msgSend就负责消息发送。

1
2
3
OBJC_EXPORT id _Nullable
objc_msgSendSuper(struct objc_super * _Nonnull super, SEL _Nonnull op, ...)
OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0);

可以看到传入的是一个objc_super结构体,结构如下:

1
2
3
4
5
6
7
struct objc_super {
/// Specifies an instance of a class.
__unsafe_unretained _Nonnull id receiver;

__unsafe_unretained _Nonnull Class super_class;
/* super_class is the first class to search */
};

receiver:消息接收者,是当前class
super_class :父类,从父类开始寻找方法,而略过当前类
所以 调用[super xxxx] 并不是说父类来调用方法,只是从父类开始寻找方法,然后发送到当前class,最终作用对象,还是当前class 。

消息发送的主要步骤

  1. 先在cache中查找imp,找到了返回imp
  2. 在当前class的method list中查找有无imp , 在class的方法列表methods中,根据SEL查找对应的imp

  3. 找到了 ,将imp存储到当前class 的cache中

  4. 在class的所有super classes中查找imp(先看Super class的cache,再看super class的方法列表)

  5. 找到了,同3

  6. 均未找到imp,进入动态方法解析流程resolveMethod

消息转发

没有方法的实现,程序会在运行时挂掉并抛出 unrecognized selector sent to … 的异常。但在异常抛出前,Objective-C 的运行时会给你三次拯救程序的机会:

  • Method resolution
  • Fast forwarding
  • Normal forwarding

动态方法解析: Method Resolution

首先,Objective-C 运行时会调用以下方法,让你有机会提供一个函数实现。如果你添加了函数并返回 YES, 那运行时系统就会重新启动一次消息发送的过程。

1
2
+ (BOOL)resolveInstanceMethod:(SEL)sel {} (实例方法)
+ (BOOL)resolveClassMethod:(SEL)sel {} (类方法)

可以以下实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
void eatMethod(id obj, SEL _cmd)  
{
NSLog(@"eat...");
}

+ (BOOL)resolveInstanceMethod:(SEL)aSEL
{
if(aSEL == @selector(eat:)){
class_addMethod([self class], aSEL, (IMP)eatMethod, "v@:");
return YES;
}
return [super resolveInstanceMethod];
}

我们只需要在 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
2
3
4
5
6
- (id)forwardingTargetForSelector:(SEL)aSelector {
if(aSelector == @selector(eat:)){
return [[CatClass alloc] init];
}
return [super forwardingTargetForSelector:aSelector];
}

完整消息转发: Normal Forwarding

1
2
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {}
- (void)forwardInvocation:(NSInvocation *)anInvocation {}

第一个要求返回一个方法签名,第二个方法转发具体的实现。二者相互依赖,只有返回了正确的方法签名,才会执行第二个方法。

forwardInvocation:方法就是一个不能识别消息的分发中心,将这些不能识别的消息转发给不同的消息对象,或者转发给同一个对象,再或者将消息翻译成另外的消息,亦或者简单的处理掉某些消息,因此没有响应也不会报错。例如:我们可以为了避免直接闪退,可以当消息没法处理时在这个方法中给用户一个提示,也不失为一种友好的用户体验。

其中,参数invocation是从哪来的?在forwardInvocation:消息发送前,runtime系统会向对象发送methodSignatureForSelector:消息,并取到返回的方法签名用于生成NSInvocation对象。所以重写forwardInvocation:的同时也要重写methodSignatureForSelector:方法,否则会抛出异常。当一个对象由于没有相应的方法实现而无法响应某个消息时,运行时系统将通过forwardInvocation:消息通知该对象。每个对象都继承了forwardInvocation:方法,我们可以将消息转发给其它的对象。

相关实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wundeclared-selector"
if (aSelector == @selector(eat:)) {
#pragma clang diagnostic pop
return [NSMethodSignature signatureWithObjCTypes:"v@:@"];
}
return [super methodSignatureForSelector:aSelector];
}

- (void)forwardInvocation:(NSInvocation *)anInvocation {
Person *person = [Person new];
Animal *animal = [Animal new];
if ([person respondsToSelector:anInvocation.selector]) {
[anInvocation invokeWithTarget:person];
}
if ([animal respondsToSelector:anInvocation.selector]) {
[anInvocation invokeWithTarget:animal];
}
}

如果三次都没有处理,就会抛出异常

1
unrecognized selector sent to instance 0x6d9f817072b1

模拟多继承

转发和继承相似,可用于为OC编程添加一些多继承的效果,一个对象把消息转发出去,就好像他把另一个对象中放法接过来或者“继承”一样。消息转发弥补了objc不支持多继承的性质,也避免了因为多继承导致单个类变得臃肿复杂。

相关代码实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN

@interface GDTMultipleDelegateProxy : NSObject

+ (instancetype)share;
/// 把目标类添加到管理类里面
- (void)addTarget:(id)target protocol:(id)protocol;
/// 移除某个目标类
- (void)removeProtocol:(id)protocol;
/// 移除所有目标类
- (void)removeAll;

@end

NS_ASSUME_NONNULL_END
#import "GDTMultipleDelegateProxy.h"
#import <objc/runtime.h>
#import <objc/message.h>

@interface GDTMultipleDelegateProxy ()

@property (nonatomic, strong) NSMutableDictionary *refTargets;

@end

@implementation GDTMultipleDelegateProxy

static GDTMultipleDelegateProxy *manager = nil;

+ (instancetype)share {
if (!manager) {
manager = [[GDTMultipleDelegateProxy alloc] init];
}
return manager;
}

- (void)addTarget:(id)target protocol:(id)protocol {
if (![self.refTargets.allKeys containsObject:protocol]) {
NSPointerArray *targets = [NSPointerArray weakObjectsPointerArray];
[targets addPointer:(__bridge void * _Nullable)(target)];
[self.refTargets setObject:targets forKey:protocol];
} else {
NSPointerArray *targets = [self.refTargets objectForKey:protocol];
if ([targets.allObjects containsObject:target]) {return;}
[targets addPointer:(__bridge void * _Nullable)(target)];
}
}

/// 方法签名
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
NSMethodSignature *sig = [super methodSignatureForSelector:aSelector];
if (!sig) {
for (id key in self.refTargets.allKeys) {
NSPointerArray *targets = [self.refTargets objectForKey:key];
for (id obj in targets) {
if ((sig = [obj methodSignatureForSelector:aSelector])) {
break;
}
}
}
}
return sig;
}

/// 消息转发
- (void)forwardInvocation:(NSInvocation *)anInvocation {
for (NSString *key in self.refTargets.allKeys) {
NSPointerArray *targets = [self.refTargets objectForKey:key];
const char *name = [key UTF8String];
Protocol *protocol = objc_getProtocol(name);
NSAssert(protocol!=nil, @"不存在的协议");
struct objc_method_description protocol_method_description = protocol_getMethodDescription(protocol, anInvocation.selector, NO, YES);
NSLog(@"===%@==%s=",NSStringFromSelector(protocol_method_description.name),protocol_method_description.types);
if (protocol_method_description.name != nil) {
for (id obj in targets) {
if ([obj respondsToSelector:anInvocation.selector]) {
[anInvocation invokeWithTarget:obj];
}
}
}
}
}

- (void)removeProtocol:(id)protocol {
if ([self.refTargets.allKeys containsObject:protocol]) {
[_refTargets removeObjectForKey:protocol];
}
}

- (void)removeAll {
[self.refTargets removeAllObjects];
}

/// 判断目标类是否实现该协议
- (BOOL)respondsToSelector:(SEL)aSelector {
if ([super respondsToSelector:aSelector]) {
return YES;
}

for (id key in self.refTargets.allKeys) {
NSPointerArray *targets = [self.refTargets objectForKey:key];
for (id obj in targets) {
if ([obj respondsToSelector:aSelector]) {
return YES;
}
}
}
return NO;
}


- (NSMutableDictionary *)refTargets {
if (!_refTargets) {
_refTargets = [NSMutableDictionary dictionary];
}
return _refTargets;
}

- (Class)getMeterClassObjc:(id)objc {
return object_getClass(objc);
}

//给objc的deallocz转发一个新方法
- (void)cy_dealloc{
NSLog(@"=====%s====",__func__);
}

static void cy_dealloc(id self ,SEL _cmd){
NSLog(@"====%s=%@=",__func__,self);
}

@end
------ 本文结束------
0%