今天分享如何对类方法进行消息的转发.
resolveClassMethod
NSObject 提供了 resolveClassMethod 来让开发者在里面动态添加一个类方法.
类方法的转发流程和实例方法转发的流程大致一样, 唯独不同的是需要重写的方法(NSObject中)的不一样.
当时我在写 Demo, 以为只需要将 resolveInstanceMethod 改为 resolveClassMethod 就万事大吉了即重写下面几个方法就可以解决问题, 事实证明这样是不行的.
+resolveClassMethod -forwardingTargetForSelector -methodSignatureForSelector -forwardInvocation -doesNotRecognizeSelector: |
网上很多博文并没有深入的探讨关于类方法转发的流程, 只是在介绍实例方法转发的流程的同时, 一笔带过类方法转发.
通过对 NSObject.mm 源码的查看, 可以看到对应上面的几个方法都有类方法. 如下:
>1. +resolveClassMethod 2. +forwardingTargetForSelector 3. +methodSignatureForSelector 4. +forwardInvocation 5. +doesNotRecognizeSelector: |
重新这几个方法才是解决问题的关键.
现在我们重写 resolveClassMethod, 如下.
ViewController.m
#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."); } 模拟调用 - (void)viewDidLoad { [super viewDidLoad]; // 运行类方法 SEL selector = NSSelectorFromString(sPerformClassMethodName); SuppressPerformSelectorLeakWarning( [[self class] performSelector:selector withObject:nil]; ); } |
关于 SuppressPerformSelectorLeakWarning 可以参考 OC-RunTime: 消息转发之实例方法的转发流程[实例讲解].
将动态添加的方法让 proxyMethod 来执行, 显示结果达到预期.
---veryitman--- 1--- +resolveClassMethod ---veryitman--- +proxyMethod of class's method for OC. |
创建被转发者
MZTempObj.m
@implementation MZTempObj /// 类方法 + (void)veryClassMethod { NSLog(@"---veryitman--- veryClassMethod"); } @end |
这里有类方法的一个实现 veryClassMethod.
重写其他的转发函数
同理将 resolveClassMethod 修改一下, 为了保证流程继续.
示例代码如下:
+ (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)); } |
执行后, 控制台输出日志:
---veryitman--- 1--- +resolveClassMethod. selector: veryClassMethod ---veryitman--- 2--- +forwardingTargetForSelector ---veryitman--- veryClassMethod |
这里注意一下
将代码中 注意1 注意2 等部分可以自行打开测试一下, 然后将 MZTempObj.m 中的类方法(+veryClassMethod)改为实例方法(-veryClassMethod), 也是可以的, 这样就达到了将类方法转发给实例方法的效果.
修改一下 forwardingTargetForSelector 中的实现, 可以看到 4, 5也会执行.
+ (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; } |
---veryitman--- 1--- +resolveClassMethod. selector: veryClassMethod ---veryitman--- 2--- +forwardingTargetForSelector ---veryitman--- 3--- +methodSignatureForSelector ---veryitman--- 1--- +resolveClassMethod. selector: _forwardStackInvocation: ---veryitman--- 4--- +forwardInvocation ---veryitman--- veryClassMethod |
同理我们可以得到类方法的消息转发流程图, 如下图所示:
上文内容不用于商业目的,如涉及知识产权问题,请权利人联系博为峰小编(021-64471599-8017),我们将立即处理。