发布于 

OC-RunTime: 消息转发之实例方法的转发流程[实例讲解]

OC-RunTime: 消息转发之实例方法的转发流程 分享了消息转发的流程, 本次结合实际例子继续分析一下消息转发流程.

发送不存在的消息

在 ViewController 的 viewDidLoad 中运行 veryTestMethod 方法.

ViewController.m

1
2
3
4
5
6
7
8
9
10
11
12
13
static NSString * const sPerformInstanceMethodName = @"veryTestMethod";

- (void)viewDidLoad {

[super viewDidLoad];

// 运行实例方法
SEL selector = NSSelectorFromString(sPerformInstanceMethodName);

SuppressPerformSelectorLeakWarning(
[self performSelector:selector withObject:nil];
);
}

其中, SuppressPerformSelectorLeakWarning 是定义的一个宏.

1
2
3
4
5
6
7
#define SuppressPerformSelectorLeakWarning(Stuff) \
do { \
_Pragma("clang diagnostic push") \
_Pragma("clang diagnostic ignored \"-Warc-performSelector-leaks\"") \
Stuff; \
_Pragma("clang diagnostic pop") \
} while (0)

在 ViewController 中我并没有写 veryTestMethod 这个函数, 只是借助 performSelector 动态执行, 如果编译运行直接会 crash.

可以查看 NSObject.mm 源码, 里面关于消息转发的几个重要函数都写着 _objc_fatal, 可谓是招招毙命.

紧接着, 我们可以借助 resolveInstanceMethod 来完成消息转发给 ViewController.

resolveInstanceMethod 转发

重写 NSObject 中的 resolveInstanceMethod 函数.

+resolveInstanceMethod

ViewController.m

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
// 记得导入 RunTime 头文件
#import <objc/runtime.h>

+ (BOOL)resolveInstanceMethod:(SEL)sel
{
NSLog(@"---veryitman--- 1--- +resolveInstanceMethod");

NSString *methodName = NSStringFromSelector(sel);

if ([sPerformInstanceMethodName isEqualToString:methodName]) {

SEL proxySelector = NSSelectorFromString(@"proxyMethod");
IMP impletor = class_getMethodImplementation(self, proxySelector);

// 获取实例方法
Method method = class_getInstanceMethod(self, proxySelector);
const char *types = method_getTypeEncoding(method);

// 添加 OC 的函数
class_addMethod([self class], sel, impletor, types);

return YES;
}

return [super resolveInstanceMethod:sel];
}

ViewController 中实现的 proxyMethod

1
2
3
4
5
// OC 实现
- (void)proxyMethod
{
NSLog(@"---veryitman--- -proxyMethod of instance's method for OC.");
}

resolveInstanceMethod 中动态添加了 veryTestMethod 方法, 并让 proxyMethod 函数来实现(IMP).

运行可以看到, 程序并没有 crash, 成功的执行了 proxyMethod.

1
2
---veryitman--- 1--- +resolveInstanceMethod
---veryitman--- -proxyMethod of instance's method for OC.

到此为止, 我们已经看到动态添加一个方法的实现并成功运行的完整例子.

接下来, 我们将转发给其他对象 MZTempObj 来执行.

自定义被转发的对象

MZTempObj.m

1
2
3
4
5
6
@implementation MZTempObj

- (void)veryTestMethod
{
NSLog(@"---veryitman--- veryTestMethod");
}

veryTestMethod 就是我们要转发对应的消息.

消息转发实践

接下来我们把向 ViewController 发送 veryTestMethod 的消息转发给 MZTempObjveryTestMethod 方法.

继续重写下面函数, 不过 resolveInstanceMethod 要稍微改造一下, 才能达到我们实践的目的.

  1. +resolveInstanceMethod
  2. -forwardingTargetForSelector
  3. -methodSignatureForSelector
  4. -forwardInvocation
  5. -doesNotRecognizeSelector:

ViewController.m

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
// 记得导入 RunTime 头文件
#import <objc/runtime.h>

+ (BOOL)resolveInstanceMethod:(SEL)sel
{
NSLog(@"---veryitman--- 1--- +resolveInstanceMethod: %@", NSStringFromSelector(sel));

NSString *methodName = NSStringFromSelector(sel);

// 这里故意为之, 让流程往下走
if ([@"" isEqualToString:methodName]) {

SEL proxySelector = NSSelectorFromString(@"proxyMethod");
IMP impletor = class_getMethodImplementation(self, proxySelector);

// 获取实例方法
Method method = class_getInstanceMethod(self, proxySelector);
const char *types = method_getTypeEncoding(method);

// 添加 OC 的函数
class_addMethod([self class], sel, impletor, types);

return YES;
}

return [super resolveInstanceMethod:sel];
}

/// 转发给对应的某个对象来执行 aSelector
- (id)forwardingTargetForSelector:(SEL)aSelector
{
NSLog(@"---veryitman--- 2--- -forwardingTargetForSelector");

NSString *selectorName = NSStringFromSelector(aSelector);

if ([sPerformInstanceMethodName isEqualToString:selectorName]) {

// 让 MZTempObj 去执行 aSelector, 实现消息的转发
MZTempObj *myobject = [[MZTempObj alloc] init];

return myobject;
}

id obj = [super forwardingTargetForSelector:aSelector];

return obj;
}

- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
{
NSLog(@"---veryitman--- 3--- -methodSignatureForSelector");

// 找出对应的 aSelector 签名
NSMethodSignature *signature = [super methodSignatureForSelector:aSelector];

if (nil == signature) {

// 是否有 aSelector
if ([MZTempObj instancesRespondToSelector:aSelector]) {
signature = [MZTempObj instanceMethodSignatureForSelector:aSelector];
}
}

return signature;
}

- (void)forwardInvocation:(NSInvocation *)anInvocation
{
NSLog(@"---veryitman--- 4--- -forwardInvocation");

if ([MZTempObj instancesRespondToSelector:anInvocation.selector]) {
[anInvocation invokeWithTarget:[[MZTempObj alloc] init]];
}
else {
[super forwardInvocation:anInvocation];
}
}

- (void)doesNotRecognizeSelector:(SEL)aSelector
{
NSLog(@"---veryitman--- 5--- -doesNotRecognizeSelector: %@", NSStringFromSelector(aSelector));
}

运行程序, 控制台打印结果如下:

1
2
3
---veryitman--- 1--- +resolveInstanceMethod
---veryitman--- 2--- -forwardingTargetForSelector
---veryitman--- veryTestMethod

这里对照之前的流程图是完全符合的, 那么怎么让其执行 3 和 4 呢? 很简单, 修改一下 forwardingTargetForSelector 里面的实现即可.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
- (id)forwardingTargetForSelector:(SEL)aSelector
{
NSLog(@"---veryitman--- 2--- -forwardingTargetForSelector");

NSString *selectorName = NSStringFromSelector(aSelector);

// 故意为之
if ([@"" isEqualToString:selectorName]) {

// 让 MZTempObj 去执行 aSelector, 实现消息的转发
MZTempObj *myobject = [[MZTempObj alloc] init];

return myobject;
}

id obj = [super forwardingTargetForSelector:aSelector];

return obj;
}

再次执行看结果:

1
2
3
4
5
6
---veryitman--- 1--- +resolveInstanceMethod veryTestMethod
---veryitman--- 2--- -forwardingTargetForSelector
---veryitman--- 3--- -methodSignatureForSelector
---veryitman--- 1--- +resolveInstanceMethod: _forwardStackInvocation:
---veryitman--- 4--- -forwardInvocation
---veryitman--- veryTestMethod

注意: 这里在 3后面会多了一个 1--- resolveInstanceMethod 的打印, 是系统调用的, 此时对应的 sel 是 _forwardStackInvocation.

如果不去重写 methodSignatureForSelector 打印结果如下:

1
2
3
---veryitman--- 1--- +resolveInstanceMethod
---veryitman--- 2--- -forwardingTargetForSelector
---veryitman--- 5--- -doesNotRecognizeSelector: veryTestMethod

参考文档

1.Apple RunTime 源码 objc4-723.tar.gz

2.Message Forwarding

推荐

OC-RunTime: 消息转发之实例方法的转发流程

OC-RunTime: 消息转发之类方法的转发流程

OC-RunTime: 总结消息转发中用到的知识点

点击下载文中完整的 Demo.


扫码关注,你我就各多一个朋友~


本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处。

veryitman