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

本篇是「消息转发」系列的第三篇, 在 OC-RunTime: 消息转发之实例方法的转发流程OC-RunTime: 消息转发之实例方法的转发流程[实例讲解] 中分享了实例方法的转发流程.

今天分享如何对类方法进行消息的转发.

resolveClassMethod

NSObject 提供了 resolveClassMethod 来让开发者在里面动态添加一个类方法.

类方法的转发流程和实例方法转发的流程大致一样, 唯独不同的是需要重写的方法(NSObject中)的不一样.

当时我在写 Demo, 以为只需要将 resolveInstanceMethod 改为 resolveClassMethod 就万事大吉了即重写下面几个方法就可以解决问题, 事实证明这样是不行的.

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

网上很多博文并没有深入的探讨关于类方法转发的流程, 只是在介绍实例方法转发的流程的同时, 一笔带过类方法转发机制和流程.

通过对 NSObject.mm 源码的查看, 可以看到对应上面的几个方法都有类方法. 如下:

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

重新这几个方法才是解决问题的关键.

现在我们重写 resolveClassMethod, 如下.


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
#import <objc/runtime.h>
static NSString * const sPerformClassMethodName = @"veryClassMethod";
+ (BOOL)resolveClassMethod:(SEL)sel
{
NSLog(@"---veryitman--- 1--- +resolveClassMethod");
NSString *methodName = NSStringFromSelector(sel);
if ([sPerformClassMethodName isEqualToString:methodName]) {
// 获取 MetaClass
Class predicateMetaClass = objc_getMetaClass([NSStringFromClass(self) UTF8String]);
// 根据 metaClass 获取方法的实现
IMP impletor = class_getMethodImplementation(predicateMetaClass, @selector(proxyMethod));
// 获取类方法
Method predicateMethod = class_getClassMethod(predicateMetaClass, @selector(proxyMethod));
const char *encoding = method_getTypeEncoding(predicateMethod);
// 动态添加类方法
class_addMethod(predicateMetaClass, sel, impletor, encoding);
return YES;
}
return [super resolveClassMethod:sel];
}
+ (void)proxyMethod
{
NSLog(@"---veryitman--- +proxyMethod of class's method for OC.");
}

模拟调用

1
2
3
4
5
6
7
8
9
10
- (void)viewDidLoad
{
[super viewDidLoad];
// 运行类方法
SEL selector = NSSelectorFromString(sPerformClassMethodName);
SuppressPerformSelectorLeakWarning(
[[self class] performSelector:selector withObject:nil];
);
}

关于 SuppressPerformSelectorLeakWarning 可以参考 OC-RunTime: 消息转发之实例方法的转发流程[实例讲解].

将动态添加的方法让 proxyMethod 来执行, 显示结果达到预期.

1
2
---veryitman--- 1--- +resolveClassMethod
---veryitman--- +proxyMethod of class's method for OC.

创建被转发者

MZTempObj.m

1
2
3
4
5
6
7
8
9
@implementation MZTempObj
/// 类方法
+ (void)veryClassMethod
{
NSLog(@"---veryitman--- veryClassMethod");
}
@end

这里有类方法的一个实现 veryClassMethod.

重写转发消息的函数

同理将 resolveClassMethod 修改一下, 为了保证流程继续.

示例代码如下:

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
+ (BOOL)resolveClassMethod:(SEL)sel
{
NSLog(@"---veryitman--- 1--- +resolveClassMethod. selector: %@", NSStringFromSelector(sel));
NSString *methodName = NSStringFromSelector(sel);
// 这里故意将 sPerformClassMethodName 改为 @"", 为了流程往下走
if ([@"" isEqualToString:methodName]) {
// 获取 MetaClass
Class predicateMetaClass = objc_getMetaClass([NSStringFromClass(self) UTF8String]);
// 根据 metaClass 获取方法的实现
IMP impletor = class_getMethodImplementation(predicateMetaClass, @selector(proxyMethod));
// 获取类方法
Method predicateMethod = class_getClassMethod(predicateMetaClass, @selector(proxyMethod));
const char *encoding = method_getTypeEncoding(predicateMethod);
// 动态添加类方法
class_addMethod(predicateMetaClass, sel, impletor, encoding);
return YES;
}
return [super resolveClassMethod:sel];
}
+ (id)forwardingTargetForSelector:(SEL)aSelector
{
NSLog(@"---veryitman--- 2--- +forwardingTargetForSelector");
NSString *selectorName = NSStringFromSelector(aSelector);
if ([sPerformClassMethodName isEqualToString:selectorName]) {
// 注意1: 也可在此转发实例方法
#if 0
// 让 MZTempObj 去执行 aSelector, 实现消息的转发
MZTempObj *myobject = [[MZTempObj alloc] init];
return myobject;
#endif
// 转发类方法对应返回类对象
return [MZTempObj class];
}
id obj = [super forwardingTargetForSelector:aSelector];
return obj;
}
+ (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
{
NSLog(@"---veryitman--- 3--- +methodSignatureForSelector");
// 找出对应的 aSelector 签名
NSMethodSignature *signature = [super methodSignatureForSelector:aSelector];
// 注意2: 也可以在此获取实例方法的签名
#if 0
if (nil == signature) {
// 是否有 aSelector
if ([MZTempObj instancesRespondToSelector:aSelector]) {
signature = [MZTempObj instanceMethodSignatureForSelector:aSelector];
}
}
return signature;
#endif
if (nil == signature) {
// 是否有 aSelector
if ([MZTempObj respondsToSelector:aSelector]) {
//methodSignatureForSelector 可以获取类方法和实例方法的签名
//instanceMethodSignatureForSelector只能获取实例方法的签名
signature = [MZTempObj methodSignatureForSelector:aSelector];
}
}
return signature;
}
+ (void)forwardInvocation:(NSInvocation *)anInvocation
{
NSLog(@"---veryitman--- 4--- +forwardInvocation");
// 注意3: 也可以调用实例方法
#if 0
if ([MZTempObj instancesRespondToSelector:anInvocation.selector]) {
[anInvocation invokeWithTarget:[[MZTempObj alloc] init]];
}
else {
[super forwardInvocation:anInvocation];
}
return;
#endif
if ([MZTempObj respondsToSelector:anInvocation.selector]) {
// 这里转发的是 MZTempObj Class, 不是对象
[anInvocation invokeWithTarget:[MZTempObj class]];
}
else {
[super forwardInvocation:anInvocation];
}
}
+ (void)doesNotRecognizeSelector:(SEL)aSelector
{
NSLog(@"---veryitman--- 5--- +doesNotRecognizeSelector: %@", NSStringFromSelector(aSelector));
}

执行后, 控制台输出日志:

1
2
3
---veryitman--- 1--- +resolveClassMethod. selector: veryClassMethod
---veryitman--- 2--- +forwardingTargetForSelector
---veryitman--- veryClassMethod

这里注意一下

将代码中 注意1 注意2 等部分可以自行打开测试一下, 然后将 MZTempObj.m 中的类方法(+veryClassMethod)改为实例方法(-veryClassMethod), 也是可以的, 这样就达到了将类方法转发给实例方法的效果.

修改一下 forwardingTargetForSelector 中的实现, 可以看到 4, 5也会执行.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
+ (id)forwardingTargetForSelector:(SEL)aSelector
{
NSLog(@"---veryitman--- 2--- +forwardingTargetForSelector");
NSString *selectorName = NSStringFromSelector(aSelector);
if ([@"" isEqualToString:selectorName]) {
// 注意1: 也可在此转发实例方法
#if 0
// 让 MZTempObj 去执行 aSelector, 实现消息的转发
MZTempObj *myobject = [[MZTempObj alloc] init];
return myobject;
#endif
// 转发类方法对应返回类对象
return [MZTempObj class];
}
id obj = [super forwardingTargetForSelector:aSelector];
return obj;
}
1
2
3
4
5
6
---veryitman--- 1--- +resolveClassMethod. selector: veryClassMethod
---veryitman--- 2--- +forwardingTargetForSelector
---veryitman--- 3--- +methodSignatureForSelector
---veryitman--- 1--- +resolveClassMethod. selector: _forwardStackInvocation:
---veryitman--- 4--- +forwardInvocation
---veryitman--- veryClassMethod

同理我们可以得到类方法的消息转发流程图, 如下图所示:

1

推荐

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

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

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

点击下载文中完整的 Demo.

坚持原创技术分享!