iOS之runtime详解api(二)

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

3.objc_method or Method

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

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的结构体:api

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里面也有:bash

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'
复制代码

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

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);
复制代码

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

在项目里面,新增一个类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里面去获取这四个方法。code

- (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,喜欢的能够点个星。

相关文章
相关标签/搜索