iOS之runtime详解api(二)

上一篇我们讲解了runtime里面关于类和分类的函数,那么,我们这一篇就讲解下关于Method的那些函数。

3.objc_method or Method

objc_method或者Method(这两个其实是同一个)这个结构体在runtime.h文件里并没有详细的告诉我们其中的成员变量,这个在这个阶段也不是很重要,后面我将会在分析runtime源码的时候,再去分析这个结构体,现在我们只需知道这个是关于函数的结构体。 除了objc_method这个结构体还有一个objc_method_description,结构如下:

struct objc_method_description {
    SEL _Nullable name;               /**< The name of the method */
    char * _Nullable types;           /**< The types of the method arguments */
};
复制代码

name在这里指的是函数名称,types指的是函数的参数返回值的类型。 我们就要通过这个method_getDescription这个方法,可以获得objc_method_description的结构体:

struct objc_method_description * _Nonnull
method_getDescription(Method _Nonnull m)
OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);
复制代码

通过传入Method返回objc_method_description: 除了这个objc_method_description结构体,我们还要了解2个概念,SELIMP: SEL:函数的选择器,一般用函数名进行绑定。 IMP:函数的地址,用过这个可以执行函数。 关于SEL我们应该很熟悉,在OC中,有个方法是SELNSString互相转换,方法是SEL NSSelectorFromString(NSString *aSelectorName)NSString *NSStringFromSelector(SEL aSelector),在runtime里面也有:

const char * _Nonnull
sel_getName(SEL _Nonnull sel)
OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0);

SEL _Nonnull
sel_registerName(const char * _Nonnull str)
OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0);
复制代码

这两个方法就可以是上面两个方法的替代方法。现在准备写个方法如何打印关于Method的信息:

-(void)logMethodDescription:(Method)method {
    if (method) {
        struct objc_method_description * description = method_getDescription(method);
        SEL selector = description->name;
        char* types = description->types;
        NSLog(@"selector=%s,type=%s", sel_getName(selector),types);
    } else {
        NSLog(@"Method 为 null");
    }
}
复制代码

后面我们去打印Method相关的信息就可以调用logMethodDescription方法。 另外,runtime为了能更好的表示函数的参数和返回值的结构,特别有一张表:

#define _C_ID       '@'
#define _C_CLASS    '#'
#define _C_SEL      ':'
#define _C_CHR      'c'
#define _C_UCHR     'C'
#define _C_SHT      's'
#define _C_USHT     'S'
#define _C_INT      'i'
#define _C_UINT     'I'
#define _C_LNG      'l'
#define _C_ULNG     'L'
#define _C_LNG_LNG  'q'
#define _C_ULNG_LNG 'Q'
#define _C_FLT      'f'
#define _C_DBL      'd'
#define _C_BFLD     'b'
#define _C_BOOL     'B'
#define _C_VOID     'v'
#define _C_UNDEF    '?'
#define _C_PTR      '^'
#define _C_CHARPTR  '*'
#define _C_ATOM     '%'
#define _C_ARY_B    '['
#define _C_ARY_E    ']'
#define _C_UNION_B  '('
#define _C_UNION_E  ')'
#define _C_STRUCT_B '{'
#define _C_STRUCT_E '}'
#define _C_VECTOR   '!'
#define _C_CONST    'r'
复制代码

这些代表什么等我们后面用到再说。 我们知道方法分为实例方法和类方法,那么怎么获得他们呢,就用这两个函数:

Method _Nullable
class_getInstanceMethod(Class _Nullable cls, SEL _Nonnull name)
OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0);

Method _Nullable
class_getClassMethod(Class _Nullable cls, SEL _Nonnull name)
OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0);
复制代码

这两个函数分别代表,获取某个类的某个方法。

在项目里面,新增一个类Car,定义两个实例方法-(void)function1-(void)function2,两个类方法+(void)function1_class+(void)function2_class。但是只实现-(void)function1+(void)function1_class。我们看下是否都可以找到。

@interface Car : NSObject
-(void)function1;
-(void)function2;
+(void)function1_class;
+(void)function2_class;
@end

@implementation Car

-(void)function1 {
    NSLog(@"----function1----");
}
+(void)function1_class {
    NSLog(@"----function1_class----");
}
@end
复制代码

然后在ViewController里面去获取这四个方法。

- (void)getMethod {
    //获取function1
    SEL selector = sel_registerName("function1");
    Method method = class_getInstanceMethod(objc_getClass("Car"), selector);
    [self logMethodDescription:method];
    
    //获取function1
    SEL selector1 = sel_registerName("function2");
    Method method1 = class_getInstanceMethod(objc_getClass("Car"), selector1);
    [self logMethodDescription:method1];
    
    //获取function1_class
    SEL selector2 = sel_registerName("function1_class");
    Method method2 = class_getInstanceMethod(objc_getMetaClass("Car"), selector2);
    [self logMethodDescription:method2];
    
    //获取function2_class
    SEL selector3 = sel_registerName("function2_class");
    Method method3 = class_getInstanceMethod(objc_getMetaClass("Car"), selector3);
    [self logMethodDescription:method3];
}
复制代码

打印结果(如果只声明方法,但是不实现Method就会为null):

2019-02-25 11:40:07.336465+0800 Runtime-Demo[41836:4488074] selector=function1,type=v16@0:8
2019-02-25 11:40:07.336513+0800 Runtime-Demo[41836:4488074] Method 为 null
2019-02-25 11:40:07.336523+0800 Runtime-Demo[41836:4488074] selector=function1_class,type=v16@0:8
2019-02-25 11:40:07.336539+0800 Runtime-Demo[41836:4488074] Method 为 null
复制代码

首先,我们看到了没有实现的方法是找不到的,只有实现的方法才能找到,另外,我们发现找类方法时候传入的cls是通过objc_getMetaClass这个方法获得的,为什么?我们在上一篇中说过了,类方法是存在元类对象里面,而实例方法是存在类对象里面。然后我们再看看打印出来的type是一段奇怪的符号v16@0:8,是不是看起来很奇怪,在这里我就直接告诉你答案:

  • 这个方法返回值是void(可以从上面的表格里面去找)
  • 这个方法第一个参数是id类型(所有方法都默认有2个参数,第一个是target,第二个selector
  • 第二个参数是SEL类型
  • 这个方法所有参数的长度是16个字节
  • 第0个字节开始是第一个参数
  • 第8个字节开始是第二个参数 如果不信?你可以去尝试更多的方法,给方法加参数等等。

那我们继续,既然可以获得方法的结构体,那么也可以获得方法的地址,这可以让我们对方法进行调用:

IMP _Nullable
class_getMethodImplementation(Class _Nullable cls, SEL _Nonnull name) 
OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);

IMP _Nonnull
method_getImplementation(Method _Nonnull m) 
OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);

复制代码

这两个方法都可以获得IMP,只是传参不同,第一种更直接一点。 我们依旧以-(void)function1+(void)function1_class为测试对象,看看能否进行调用

-(void)getMethodImplementation {
    IMP imp1 = class_getMethodImplementation(objc_getClass("Car"), sel_registerName("function1"));
    imp1();
    
    IMP imp2 = class_getMethodImplementation(objc_getMetaClass("Car"), sel_registerName("function1_class"));
    imp2();
    
    Method method1 = class_getInstanceMethod(objc_getClass("Car"), sel_registerName("function1"));
    IMP imp3 = method_getImplementation(method1);
    imp3();
    
    Method method2 = class_getInstanceMethod(objc_getMetaClass("Car"), sel_registerName("function1_class"));
    IMP imp4 = method_getImplementation(method2);
    imp4();
    
}
复制代码

打印结果:

2019-02-25 13:42:16.757607+0800 Runtime-Demo[43515:4532002] ----function1----
2019-02-25 13:42:16.757647+0800 Runtime-Demo[43515:4532002] ----function1_class----
2019-02-25 13:42:16.757659+0800 Runtime-Demo[43515:4532002] ----function1----
2019-02-25 13:42:16.757667+0800 Runtime-Demo[43515:4532002] ----function1_class----
复制代码

我们可以看到打印的内容确实是实现的内容。我们不仅可以找到已经存在的IMP,而且还可以为一个方法设置新的IMP

IMP _Nonnull
method_setImplementation(Method _Nonnull m, IMP _Nonnull imp)
OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);
复制代码

传入的是你要设置的Method和新的IMP,返回的是旧的IMP; 我们设置一个功能,让+(void)function1_class实现-(void)function1方法里的实现:

-(void)setImplementation {
    Method method = class_getClassMethod(objc_getMetaClass("Car"), sel_registerName("function1_class"));
    IMP imp = class_getMethodImplementation(objc_getClass("Car"), sel_registerName("function1"));
    
    IMP oldIMP = method_setImplementation(method, imp);
    
    oldIMP();
    
    IMP newIMP = method_getImplementation(method);
    
    newIMP();
}
复制代码

运行结果:

2019-02-25 13:55:52.261447+0800 Runtime-Demo[43745:4536866] ----function1_class----
2019-02-25 13:55:52.261486+0800 Runtime-Demo[43745:4536866] ----function1----
复制代码

我们可以看到旧的IMP是原来的IMP,打印的也是原来的实现,newIMP是设置的新的IMP,打印的是-(void)function1,所以没问题。

Method _Nonnull * _Nullable
class_copyMethodList(Class _Nullable cls, unsigned int * _Nullable outCount)
复制代码

这个函数是获取某个类对象的实例对象列表或者元类对象的类方法列表,outCount是一个列表数量的指针,我们可以通过outCount知道列表的数量。 我们在Car类里面再新增两个方法-(void)function3+(void)function3_class,并且实现,实现内容就是打印他们的方法名。 然后,在ViewController去使用class_copyMethodList方法

-(void)copyMethodList {
    unsigned int count1 ;
    Method* instanceMethods = class_copyMethodList(objc_getClass("Car"), &count1);
    
    unsigned int count2 ;
    Method* classMethods = class_copyMethodList(objc_getMetaClass("Car"), &count2);
    NSLog(@"--------------------------");
    for (unsigned int i = 0; i < count1; i++) {
        Method method = instanceMethods[i];
        [self logMethodDescription:method];
    }
    NSLog(@"--------------------------");

    for (unsigned int i = 0; i < count1; i++) {
        Method method = classMethods[i];
        [self logMethodDescription:method];
    }

    free(instanceMethods);
    free(classMethods);
}
复制代码

打印结果:

2019-02-25 13:31:14.263003+0800 Runtime-Demo[43333:4527940] --------------------------
2019-02-25 13:31:14.263045+0800 Runtime-Demo[43333:4527940] selector=function1,type=v16@0:8
2019-02-25 13:31:14.263057+0800 Runtime-Demo[43333:4527940] selector=function3,type=v16@0:8
2019-02-25 13:31:14.263065+0800 Runtime-Demo[43333:4527940] --------------------------
2019-02-25 13:31:14.263073+0800 Runtime-Demo[43333:4527940] selector=function1_class,type=v16@0:8
2019-02-25 13:31:14.263082+0800 Runtime-Demo[43333:4527940] selector=function3_class,type=v16@0:8
复制代码

我们看到了,仍然是只有实现的方法才能找出来 SEL除了通过Classname获得以外,还可以通过Method来获取:

SEL _Nonnull
method_getName(Method _Nonnull m) 
OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);
复制代码

例子如下:

-(void)getSEL {
    Method method = class_getClassMethod(objc_getMetaClass("Car"), sel_registerName("function1_class"));
    SEL sel = method_getName(method);
    NSLog(@"sel = %s",sel_getName(sel));
}
复制代码

运行结果:

sel = function1_class
复制代码

可以通过method来获得SEL。关于SEL还有两个方法:

BOOL
sel_isEqual(SEL _Nonnull lhs, SEL _Nonnull rhs)
OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);

BOOL
class_respondsToSelector(Class _Nullable cls, SEL _Nonnull sel)
OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);
复制代码

sel_isEqual是用来两个SEL是否相等,class_respondsToSelector表示某个类是否响应特定的选择器。 下面我们来看一组api,这些都是关于参数或者返回值的函数:

//获得方法的格式
const char * _Nullable
method_getTypeEncoding(Method _Nonnull m) 
OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);

//获得方法参数的个数
unsigned int
method_getNumberOfArguments(Method _Nonnull m)
    OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0);

//获得返回类型
char * _Nonnull
method_copyReturnType(Method _Nonnull m)
OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);

//获得index处的参数类型
char * _Nullable
method_copyArgumentType(Method _Nonnull m, unsigned int index)
OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);

//获得返回类型
void
method_getReturnType(Method _Nonnull m, char * _Nonnull dst, size_t dst_len)
OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);

//获得index处的参数类型
void
method_getArgumentType(Method _Nonnull m, unsigned int index,
                       char * _Nullable dst, size_t dst_len)
OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);
复制代码

我们写个方法测试下这些函数:

-(void)getType {
    Method method = class_getInstanceMethod(objc_getClass("Car"), sel_registerName("function1"));
    const char* type = method_getTypeEncoding(method);
    NSLog(@"type = %s",type);
    
    int count  = method_getNumberOfArguments(method);
    NSLog(@"count = %d",count);

    char* returnChar = method_copyReturnType(method);
    NSLog(@"returnChar = %s",returnChar);
    
    for (int i = 0; i < count; i++) {
        char* argumentType = method_copyArgumentType(method,i);
        NSLog(@"第%d个参数类型为%s",i,argumentType);
    }
    
    char dst[2] = {};
    method_getReturnType(method,dst,2);
    NSLog(@"returnType = %s",dst);
    
    
    for (int i = 0; i < count; i++) {
        char dst2[2] = {};
        method_getArgumentType(method,i,dst2,2);
        NSLog(@"第%d个参数类型为%s",i,dst2);
    }
    
}
复制代码

运行结果:

2019-02-25 14:37:53.641383+0800 Runtime-Demo[44457:4553505] type = v16@0:8
2019-02-25 14:37:53.641424+0800 Runtime-Demo[44457:4553505] count = 2
2019-02-25 14:37:53.641439+0800 Runtime-Demo[44457:4553505] returnChar = v
2019-02-25 14:37:53.641459+0800 Runtime-Demo[44457:4553505] 第0个参数类型为@
2019-02-25 14:37:53.641471+0800 Runtime-Demo[44457:4553505] 第1个参数类型为:
2019-02-25 14:37:53.641495+0800 Runtime-Demo[44457:4553505] returnType = v
2019-02-25 14:37:53.641508+0800 Runtime-Demo[44457:4553505] 第0个参数类型为@
2019-02-25 14:37:53.641519+0800 Runtime-Demo[44457:4553505] 第1个参数类型为:
复制代码

返回的这些值也可以验证我们之前说的type的含义。

BOOL
class_addMethod(Class _Nullable cls, SEL _Nonnull name, IMP _Nonnull imp, 
                const char * _Nullable types) 
    OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);
复制代码

这个方法是给一个类增加方法。 我们为Car类增加一个-(void)test方法,传入的imp则是-(void)function1imptypes就是无参无返回值。

-(void)addMethod {
    IMP imp = class_getMethodImplementation(objc_getClass("Car"), sel_registerName("function1"));
    BOOL addSuccess = class_addMethod(objc_getClass("Car"), sel_registerName("test"), imp, "v@:");
    NSLog(@"增加不存在的方法 = %d",addSuccess);
    
    BOOL addSuccess2 = class_addMethod(objc_getClass("Car"), sel_registerName("function1"), imp, "v@:");
    NSLog(@"增加已存在的方法 = %d",addSuccess2);
}
复制代码

运行结果:

2019-02-25 14:55:29.265128+0800 Runtime-Demo[44747:4559426] 增加不存在的方法 = 1
2019-02-25 14:55:29.265327+0800 Runtime-Demo[44747:4559426] 增加已存在的方法 = 0
复制代码

这个方法是给一个类增加方法,增加成功,返回YES,增加失败(例如,已经存在),返回NO

IMP _Nullable
class_replaceMethod(Class _Nullable cls, SEL _Nonnull name, IMP _Nonnull imp, 
                    const char * _Nullable types) 
OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);
复制代码

这个方法有2种情况: (一)如果SEL存在,则相当于调用method_setImplementation方法 (二)如果SEL不存在,则相当于调用class_addMethod方法

void
method_exchangeImplementations(Method _Nonnull m1, Method _Nonnull m2)
OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);
复制代码

这个函数堪称黑魔法,一般在实际用途中,是和系统方法进行交换。 我们将function1function3交换一波:

-(void)exchangeImplementations {
    Method method1 = class_getInstanceMethod(objc_getClass("Car"), sel_registerName("function1"));
    Method method2 = class_getInstanceMethod(objc_getClass("Car"), sel_registerName("function3"));
    //交换方法
    method_exchangeImplementations(method1, method2);
    //此时的function1的实现应该是`function3`的实现
    IMP imp1 = method_getImplementation(method1);
    //此时的function3的实现应该是`function1`的实现
    IMP imp2 = method_getImplementation(method2);

    imp1();
    imp2();
}
复制代码

运行结果:

2019-02-25 15:16:19.507945+0800 Runtime-Demo[45112:4568994] ----function3----
2019-02-25 15:16:19.507982+0800 Runtime-Demo[45112:4568994] ----function1----
复制代码

确实交换过来了。第二篇就这么结束了,第三篇我将讲解关于属性和变量的函数。 对了,这个是demo,喜欢的可以点个星。

猜你喜欢

转载自juejin.im/post/5c7b81f86fb9a049e660f71f