zoukankan      html  css  js  c++  java
  • ios-Runtime调用私有方法

      有时在代码中会有需要调用私有方法的场景,如不想import太多头文件;想组件设计一些解耦的模块;查看别人模块中未暴露的代码进行分析等。

      在 ios 中调用私有方法有很多种方式,主要是通过Runtime去实现。下面自己也测试一下。

      新建一个Person类,Person.h中不写代码,Person.m中如下:

    #import "Person.h"
    
    @implementation Person
    
    - (void)eat
    {
        NSLog(@"xxx eat====");
    }
    
    - (void)eat:(NSString *)str str2:(NSString *)str2 str3:(NSString *)str3
    {
        NSLog(@"xxx eat====%@==%@==%@", str, str2, str3);
    }
    
    @end

    【找到该类methodLists里的方法】

      要想调用私有方法,首先要知道类有什么哪些方法。可以通过如下代码得到方法的一些信息:(不管私有还是公有,只要在该类的methodLists里)

    // 获取实例方法
    - (void)getMethods
    {
        int outCount = 0;
        Person *p = [Person new];
        Method *methods = class_copyMethodList([p class], &outCount);
        for (int i = 0; i < outCount; i ++) {
            NSLog(@"=============%d", i);
            // 获取方法名
            Method method = methods[i];
            SEL methodName = method_getName(method);
            NSLog(@"方法名= %@", NSStringFromSelector(methodName));
            
            // 获取参数
            char argInfo[512] = {};
            unsigned int argCount = method_getNumberOfArguments(method);
            for (int j = 0; j < argCount; j ++) {
                // 参数类型
                method_getArgumentType(method, j, argInfo, 512);
                NSLog(@"参数类型= %s", argInfo);
                memset(argInfo, '', strlen(argInfo));
            }
            
            // 获取方法返回值类型
            char retType[512] = {};
            method_getReturnType(method, retType, 512);
            NSLog(@"返回类型值类型= %s", retType);
        }
        free(methods);
    }

      上面代码使用runtime获取一些方法的信息:方法名,参数对应的类型,返回值类型。上面这个方法打印结果如下:

    =============0
    方法名= eat
    参数类型= @
    参数类型= :
    返回类型值类型= v
    =============1
    方法名= eat:str2:str3:
    参数类型= @
    参数类型= :
    参数类型= @
    参数类型= @
    参数类型= @
    返回类型值类型= v

      打印的类型是一些符号,不知道这是什么鬼,但其实这是苹果的类型编码。它对照的含义如下:

    v    A void —— (为空)
    @    An object (whether statically typed or typed id) —— (id类型)
    :    A method selector (SEL) —— (方法名)

      上面打印的方法信息中 eat 方法也有两个参数,实际每个方法都有两个隐藏参数。(_cmd是当前方法编号)

    【调用私有方法】

      调用私有方法有多种方式,但其实最终都大同小异。如下:

      1. 使用  performSelector  下面2和3行结果一样。这样比使用对象直接调用好处是编译器不会报错,也不用方法暴露头文件。

    1 Person *p = [Person new];
    2 [p performSelector:@selector(eat)]; // log: xxx eat====
    3 [p performSelector:NSSelectorFromString(@"eat")]; // log: xxx eat====

      2. 使用 objc_msgSend ,对比上面 objc_msgSend 好处是传递多个参数时更为方便。objc_msgSend深入学习

    Person *p = [Person new]; // 需要引用 Person.h 头文件
    objc_msgSend(p, @selector(eat:str2:str3:), @"1", @"2", @"3"); // log: eat====1==2==3

      3. 利用函数实现IMP,IMP类型结构与objc_msgSend底层是同一类型,与2中实现无差别

    Person *p = [Person new];
    IMP imp = [p methodForSelector:@selector(eat)];
    void (* tempFunc)(id target, SEL) = (void *)imp;
    tempFunc(p, @selector(eat)); // log: xxx eat====

      4. 使用类对象发送消息,上面3种方式都需要引用 Person.h 头文件,这里类对象进行调用可以解决这个问题。

    Class pClassObj = NSClassFromString(@"Person");
    objc_msgSend([pClassObj new], @selector(eat));

    【类对象】

      进入 objc.h 中查看 Class 与 Object 结构定义。每个 objc_object 下都有一个 isa 指针指向这个对象所属的 objc_class。

    /// An opaque type that represents an Objective-C class.
    typedef struct objc_class *Class;
    
    /// Represents an instance of a class.
    struct objc_object {
        Class _Nonnull isa  OBJC_ISA_AVAILABILITY;
    };

      再看一下 objc_class 定义的结构。它里面只有一个 isa 指针,下面那些属性在 objc2 中已经不可用了。

    struct objc_class {
        Class _Nonnull isa  OBJC_ISA_AVAILABILITY;
    
    #if !__OBJC2__
        Class _Nullable super_class                              OBJC2_UNAVAILABLE;
        const char * _Nonnull name                               OBJC2_UNAVAILABLE;
        long version                                             OBJC2_UNAVAILABLE;
        long info                                                OBJC2_UNAVAILABLE;
        long instance_size                                       OBJC2_UNAVAILABLE;
        struct objc_ivar_list * _Nullable ivars                  OBJC2_UNAVAILABLE;
        struct objc_method_list * _Nullable * _Nullable methodLists                    OBJC2_UNAVAILABLE;
        struct objc_cache * _Nonnull cache                       OBJC2_UNAVAILABLE;
        struct objc_protocol_list * _Nullable protocols          OBJC2_UNAVAILABLE;
    #endif
    
    } OBJC2_UNAVAILABLE;

      它里面的 isa 指向的是 MetaClass (元类)。Class 和 MetaClass :

    • 当我们对一个实例发送消息时(-开头的方法),会在该 instance 对应的类的 methodLists 里查找。
    • 当我们对一个类发送消息时(+开头的方法),会在该类的 MetaClass 的 methodLists 里查找。

      

    • 每个 Class 都有一个 isa 指针指向一个唯一的 Meta Class
    • 每一个 Meta Class 的 isa 指针都指向最上层的 Meta Class,即 NSObject 的 MetaClass,而最上层的 MetaClass 的 isa 指针又指向自己

      获取类对象

    [NSObject class];

      获取元类对象

    Class class = [NSObject class];
    Class metaClass = objc_getClass(class);

    【实例对象成员变量】

      上面可以知道,实例对象的方法存放在它对应类对象 (class) 的 methodsList 中,类方法存放它对应的元类 (metaClass) 的 methodsList 中。

      一个 new 出来的对象除了它的方法,它还有成员变量。

    • 成员变量是存放在实例对象之中

      假如有一个 Person 对象,这个实例对象结构体中存放信息应该是这样,即成员变量存放在结构体,方法信息通过 isa 去找相应的类对象。

    struct Student_IMPL {
        Class isa;
        int _age;
        int _height;
    };
  • 相关阅读:
    在其他对象上同步
    如何在一个线程环境中使用一个线程非安全的java类
    原子类
    Volatile
    Spring中的设计模式2
    Spring中的设计模式
    Struts2中的设计模式
    Struts2中的设计模式----ThreadLocal模式
    享元模式(Flyweight)
    Java类加载器的工作原理
  • 原文地址:https://www.cnblogs.com/buerjj/p/8545276.html
Copyright © 2011-2022 走看看