zoukankan      html  css  js  c++  java
  • iOS 通过反射的方式调用目标函数

    1. 目标

    工作中,需要解决这样一种问题,测试每一个目标接口的调用时间和返回值。逻辑较为通用,所以想使用反射来实现这种功能。

    2. 背景知识

    网上查了下,发现反射有两种实现方式,分别为 runtime中的objc_msgSend 和 NSInvocation。

    2.1 objc_msgSend

    具体可参考这篇文章:https://www.jianshu.com/p/ce00728204ed?utm_campaign

    试用了下,发现可以实现函数调用,但不太好额外加入其他逻辑,所以不太适用于解决我的问题。

    2.2 NSInvocation

    具体可参考这篇文章:https://juejin.cn/post/6844903542851895304 (虽然文中提到了NSProxy,但看实际代码里并未使用到) 和 https://ace.re/2017/objective-c-nsinvocation.html

    试用了下,发现基本可以达成我的目标。但有一个问题,系统提供的performSelector函数,最多仅支持传两个参数。这显然是不够的。所以我们需要把这块优化下。我尝试了两种解决方式。

    2.2.1 传参使用NSArray

    具体可参考这个篇文章:https://blog.csdn.net/a158337/article/details/50915245

    2.2.2 使用可变参数传参

    具体可参考这篇文章:https://www.jianshu.com/p/1dc0d05cb766。基本可用,但文章中获取返回值处需要优化下。看网上的说法,文章中获取返回值处可能会造成崩溃。

    3. 我的实现

    3.1 使用可变参数传参

    @implementation InterfaceProxy
    
    //方法签名
    - (id)methodSignatureForSelector:(SEL)sel {
      NSMethodSignature *signature = nil;
      if (self.obj) {
        return signature;
      }
      signature = [self.obj methodSignatureForSelector:sel];
      return signature ? signature : [super methodSignatureForSelector:sel];
    }
    
    - (NSDictionary *)performSelector:(NSObject *)obj sel:(SEL)sel withObject:(id)object,...NS_REQUIRES_NIL_TERMINATION {
      self.obj = obj;
      NSMethodSignature *signature = [self methodSignatureForSelector:sel];
    
      //根据类名以及SEL 获取方法签名的实例
      if (signature == nil) {
        NSLog(@"--- 未找到符合条件的方法 ---");
        return nil;
      }
    
      //NSInvocation是一个消息调用类,它包含了所有OC消息的成分:target、selector、参数以及返回值。
      NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature];
      invocation.target = self.obj;
      invocation.selector = sel;
      NSUInteger argCount = signature.numberOfArguments;
      // 参数必须从第2个索引开始,因为前两个已经被target和selector使用
      argCount = argCount > 2 ? argCount - 2 : 0;
      NSMutableArray *objs = [NSMutableArray arrayWithCapacity:0];
      if (object) {
        [objs addObject:object];
        va_list args;
        va_start(args, object);
        while ((object = va_arg(args, id))){
          [objs addObject:object];
        }
        va_end(args);
      }
      if (objs.count != argCount){
         NSLog(@"proxy --- 传参有误! please check it! ---");
         return nil;
      }
      //设置参数列表
      for (NSInteger i = 0; i < objs.count; i++) {
         id obj = objs[i];
         [invocation setArgument:&obj atIndex:i+2];
      }
    
      NSTimeInterval start = [TimeUtil currentTimeStr];
      [invocation invoke];
      NSTimeInterval stop = [TimeUtil currentTimeStr];
    
      NSString *selName = NSStringFromSelector(invocation.selector);
      NSLog(@"proxy selName: %@, start: %f stop:%f interval: %f", selName, start, stop, (stop - start));
      //获取返回值
      NSUInteger length = [signature methodReturnLength];
      const char *retType = signature.methodReturnType;
      id retVal;
      if (signature.methodReturnLength != 0 && signature.methodReturnLength) {
        if (!strcmp(retType, @encode(id))) {
          [invocation getReturnValue:&retVal];
        } else {
          void *buf = (void *)malloc(length);
          [invocation getReturnValue:buf];
          retVal = [self transRet:buf retType:retType];
        }
      }
      NSDictionary *dic = [[NSDictionary alloc] initWithObjectsAndKeys:selName,SELECTOR_NAME,
                           objs, INTERFACE_PARAMS,
                           [NSNumber numberWithDouble:start], START_TIME_STAMP,
                           [NSNumber numberWithDouble:stop], STOP_TIME_STAMP,
                           retVal, RET,
                           nil];
      return dic;
    }
    
    - (id)transRet:(void *)returnValue retType:(const char *)returnType {
      if (!strcmp(returnType, @encode(int))) {
        return [NSNumber numberWithInteger:*((NSInteger*)returnValue)];
      }
      if (!strcmp(returnType, @encode(BOOL))) {
        int tmp = *((BOOL *)returnValue) ? 0 : -1;
        return [NSNumber numberWithInt:tmp];
      }
      return nil;
    }
    
    @end
    

      

    调用方法:

    InterfaceProxy proxy = [InterfaceProxy alloc];
    NSDictionary *dic = [proxy performSelector:self->_member sel:@selector(funcName:param1:param2:) withObject:@"param0",@"param1",@"param2", nil];
    

    4. 待优化的问题

    此方法可适用于大多数场景,但有些地方仍不尽如人意。其中之一,就是传参必须是NSObject的子类。如果是C++的类,就传不进去。因为无法将其转为id。其二,不能方便的调用C语法的函数。只有成员方法或类方法,可以这样方便的调用。若开发中需调用的接口为C语法的接口,则调用起来会麻烦很多,不如将C语法的接口,包装成OC的风格。

  • 相关阅读:
    Haproxy 【转载】
    Nginx介绍
    Day 13 进程和线程
    运维第一课
    面试bb
    Day 12 字符串和正则表达式
    Day 11 文件和异常
    Day10 图形用户界面和游戏开发
    Day9 面向对象进阶
    day8 面向对象编程基础
  • 原文地址:https://www.cnblogs.com/sunada2005/p/14292160.html
Copyright © 2011-2022 走看看