OC-RunTime: 消息转发之类方法的转发流程
本篇是「消息转发」系列的第三篇, 在 OC-RunTime: 消息转发之实例方法的转发流程 和 OC-RunTime: 消息转发之实例方法的转发流程[实例讲解] 中分享了实例方法的转发流程.
今天分享如何对类方法进行消息的转发.
resolveClassMethod
NSObject 提供了 resolveClassMethod
来让开发者在里面动态添加一个类方法.
类方法的转发流程和实例方法转发的流程大致一样, 唯独不同的是需要重写的方法(NSObject中)的不一样.
当时我在写 Demo, 以为只需要将 resolveInstanceMethod
改为 resolveClassMethod
就万事大吉了即重写下面几个方法就可以解决问题, 事实证明这样是不行的.
- +resolveClassMethod
- -forwardingTargetForSelector
- -methodSignatureForSelector
- -forwardInvocation
- -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]) { Class predicateMetaClass = objc_getMetaClass([NSStringFromClass(self) UTF8String]); 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); if ([@"" isEqualToString:methodName]) { Class predicateMetaClass = objc_getMetaClass([NSStringFromClass(self) UTF8String]); 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]) { #if 0 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"); NSMethodSignature *signature = [super methodSignatureForSelector:aSelector]; #if 0 if (nil == signature) { if ([MZTempObj instancesRespondToSelector:aSelector]) { signature = [MZTempObj instanceMethodSignatureForSelector:aSelector]; } } return signature; #endif if (nil == signature) { if ([MZTempObj respondsToSelector:aSelector]) { signature = [MZTempObj methodSignatureForSelector:aSelector]; } } return signature; }
+ (void)forwardInvocation:(NSInvocation *)anInvocation { NSLog(@"---veryitman--- 4--- +forwardInvocation"); #if 0 if ([MZTempObj instancesRespondToSelector:anInvocation.selector]) { [anInvocation invokeWithTarget:[[MZTempObj alloc] init]]; } else { [super forwardInvocation:anInvocation]; } return; #endif if ([MZTempObj respondsToSelector:anInvocation.selector]) { [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]) { #if 0 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
|
同理我们可以得到类方法的消息转发流程图, 如下图所示:
推荐
OC-RunTime: 消息转发之实例方法的转发流程
OC-RunTime: 消息转发之实例方法的转发流程实例讲解
OC-RunTime: 总结消息转发中用到的知识点
点击下载文中完整的 Demo.
扫码关注,你我就各多一个朋友~