iOS之runtime详解api(二)

栏目: IOS · 发布时间: 5年前

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

在上一篇我们讲解了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 ,喜欢的可以点个星。


以上所述就是小编给大家介绍的《iOS之runtime详解api(二)》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Beginning Java Objects中文版从概念到代码

Beginning Java Objects中文版从概念到代码

巴克 / 万波 / 人民邮电出版社 / 2007-1 / 78.00元

《Beginning Java Objects中文版从概念到代码(第2版)》是关于软件对象和Java的,但并不是纯粹地介绍Java语言,而是强调如何从对象模型转换到功能完整的Java应用程序。书中讲述了对象基础、对象建模和模型的实现。《Beginning Java Objects中文版从概念到代码(第2版)》除了用学生注册系统(SRS)示例贯穿全书之外,还在附录中给出三个附加的案例,这些案例是每章......一起来看看 《Beginning Java Objects中文版从概念到代码》 这本书的介绍吧!

RGB转16进制工具
RGB转16进制工具

RGB HEX 互转工具

MD5 加密
MD5 加密

MD5 加密工具

RGB CMYK 转换工具
RGB CMYK 转换工具

RGB CMYK 互转工具