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

发表于:2018-4-03 17:12

字体: | 上一篇 | 下一篇 | 我要投稿

 作者:veryitman    来源:个人博客

  今天分享如何对类方法进行消息的转发.
  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),我们将立即处理。
《2023软件测试行业现状调查报告》独家发布~

关注51Testing

联系我们

快捷面板 站点地图 联系我们 广告服务 关于我们 站长统计 发展历程

法律顾问:上海兰迪律师事务所 项棋律师
版权所有 上海博为峰软件技术股份有限公司 Copyright©51testing.com 2003-2024
投诉及意见反馈:webmaster@51testing.com; 业务联系:service@51testing.com 021-64471599-8017

沪ICP备05003035号

沪公网安备 31010102002173号