OC学习Runtime之方法

坚持 成长 每日一篇

苹果官方文档查找地址:https://developer.apple.com/library/mac/navigation/
Runtime官方文档https://developer.apple.com/library/mac/documentation/Cocoa/Reference/ObjCRuntimeRef/index.html

基础数据类型

SEL

SEL:选择器,表示一个方法的selector的指针,objc_selector结构体的详细定义没有在头文件中找到。

typedef struct objc_selector *SEL;

两个类之间,不管它们是父类与子类的关系,还是之间没有这种关系,只要方法名相同,那么方法的SEL就是一样的。每一个方法都对应着一个SEL。所以在 Objective-C同一个类(及类的继承体系)中,不能存在2个同名的方法,即使参数类型不同也不行。

不同类的实例对象执行相同的selector时,会在各自的方法列表中去根据selector去寻找自己对应的IMP。

工程中的所有的SEL组成一个Set集合,Set的特点就是唯一,因此SEL是唯一的。因此,如果我们想到这个方法集合中查找某个方法时,只需要去 找到这个方法对应的SEL就行了,SEL实际上就是根据方法名hash化了的一个字符串,而对于字符串的比较仅仅需要比较他们的地址就可以了。
我们可以通过以下方法获取SEL

SEL sel_registerName(const char *str)//向runtime system注册一个方法名。如果方法名已经注册,则放回已经注册的SEL
SEL sel_getUid(const char *str)//同上
@selector(<#selector#>)//oc编译器提供的
SEL NSSelectorFromString(NSString *aSelectorName)//OC字符串转化
SEL method_getName ( Method m );//根据Method结构体获取
等等

SEL的操作函数

// 比较两个选择器
BOOL sel_isEqual ( SEL lhs, SEL rhs );
//判断方法名是否映射到某个函数实现上
BOOL sel_isMapped(SEL sel);

IMP

IMP实际上是一个函数指针,指向方法实现的首地址。其定义如下:

typedef id (*IMP)(id, SEL, ...); 

这个函数使用当前CPU架构实现的标准的C调用约定。第一个参数是指向self的指针(如果是实例方法,则是类实例的内存地址;如果是类方法,则是指向元类的指针),第二个参数是方法选择器(selector),接下来是方法的实际参数列表。

IMP原理:SEL是方法名的唯一标识,相同的方法名如果要对应不同的实现还需关联不同的实现地址,IMP就是为此而生的。当我们知道一个IMP时候就会知道他对应的SEL名。

IMP的作用:
我们就可以像调用普通的C语言函数一样来使用这个函数指针 了。
通过取得IMP,我们可以跳过Runtime的消息传递机制,直接执行IMP指向的函数实现,这样省去了Runtime消息传递过程中所做的一系列查找操作,会比直接向对象发送消息高效一些。

获取IMP的函数如下

IMP imp_implementationWithBlock(id block)//根据代码块获取IMP,其实就是代码块与IMP关联
IMP method_getImplementation(Method m) //根据Method获取IMP
[[objc Class] instanceMethodForSelector:SEL]//根据OC方式获取IMP
//待续

当我们获取一个方法的IMP时候可以直接调用IMP

IMP imp = method_getImplementation(Method m);
id objcimp(id,SEL,argument);//objc用来保存方法的返回值,id表示调用这个方法的对象,SEL是Method的选择器,argument是方法的参数。

注意:
1.对于一个对象方法其实他的第一个参数是对象本身第二个参数开始背存放在IMP的…里面。所以对于不属于任何对象的IMP我们要直接调用的参数是从IMP的第一参数id算起,忽略SEL传nil,然后接着后面的第二第三个参数。对于对象方法(包括类方法,类方法对象其实就是元类)则我们可以对id和sel直接传nil,忽略他然后直接调用该对象方法达到像使用C函数一样使用对象方法。
2.在OC里面对象方法是至少两个参数的C函数,两个参数是背隐藏起来的,即对象的自身self和方法名_cmd,我们在开发中常用到self,但是很少用到_cmd。
3.用imp掉用方法时候参数一点要传全,没有值的可以传nil,否则会出现错误或者一些想不到的结果

Method

Method定义如下:它主要是用语描述类里面的方法

typedef struct objc_method *Method;

objc_method结构体定义如下

struct objc_method {
    SEL method_name                              OBJC2_UNAVAILABLE;//方法名
    char *method_types                           OBJC2_UNAVAILABLE;//参数返回值字符串描述
    IMP method_imp                               OBJC2_UNAVAILABLE;//方法的实现
}    

我们可以看到该结构体中包含一个SEL和IMP,实际上相当于在SEL和IMP之间作了一个映射。有了Method,SEL可以通过Method找到对应的IMP,从而调用方法的实现代码。

Method操作函数如下

方法操作主要有以下函数:
// 添加方法
BOOL class_addMethod ( Class cls, SEL name, IMP imp, const char *types );

// 获取实例方法
Method class_getInstanceMethod ( Class cls, SEL name );

// 获取类方法
Method class_getClassMethod ( Class cls, SEL name );

// 获取所有方法的数组
Method * class_copyMethodList ( Class cls, unsigned int *outCount );

// 替代方法的实现
IMP class_replaceMethod ( Class cls, SEL name, IMP imp, const char *types );

// 返回方法的具体实现
IMP class_getMethodImplementation ( Class cls, SEL name );
IMP class_getMethodImplementation_stret ( Class cls, SEL name );

// 类实例是否响应指定的selector
BOOL class_respondsToSelector ( Class cls, SEL sel );

objc_method_description

objc_method_description结构体用来描述Method的一些信息,这样我么就可以直接读出来

struct objc_method_description {
    SEL name;               /**< The name of the method 方法名*/ 
    char *types;            /**< The types of the method arguments 参数类型字符串*/
};

获取函数

// 返回指定方法的方法描述结构体
struct objc_method_description * method_getDescription ( Method m );

Method相关操作函数

测试代码用到的类
Boy.h

#import <Foundation/Foundation.h>

@interface Boy : NSObject
-(void)say:(NSString*)str girl:(NSString*)girl;
@end

Boy.m

#import "Boy.h"

@implementation Boy
-(void)say:(NSString*)str girl:(NSString*)girl
{
    NSLog(@"%@",str);
    NSLog(@"%@",girl);

}
@end

1.调用指定方法的实现

id method_invoke ( id receiver, Method m, ... );

实例:

    Boy *boy = [[Boy alloc] init];
    Method method = class_getInstanceMethod([Boy class], @selector(say:girl:));
    method_invoke(boy,method,@"My love",@"sex girl");

输出:

2015-09-22 11:11:16.264 Runtime[9809:522731] My love
2015-09-22 11:11:16.264 Runtime[9809:522731] sex girl

注意:OC对于该方法有两套定义,如果我们输入method_invoke(boy,method,@”My love”,@”sex girl”);出现传入参数过多错误代码,可以通过如下配置解除错误。

在工程-Build Settings 中将Enable Strict Checking of objc_msgSend Calls 设置为NO即可

2.调用返回一个数据结构的方法的实现

void method_invoke_stret ( id receiver, Method m, ... );

这个函数没有测试成功。。。。。

3.获取方法名

SEL method_getName ( Method m );

实例:

    SEL selector = method_getName(method);
    NSLog(@"%@",NSStringFromSelector(selector));

输出:

2015-09-22 11:11:16.264 Runtime[9809:522731] say:girl:

4.返回方法的实现

IMP method_getImplementation ( Method m );

实例:

    IMP imp = method_getImplementation(method);
    imp(boy,@selector(say:girl:),@"My love",@"My girl");

输出:

2015-09-22 11:11:16.264 Runtime[9809:522731] My love
2015-09-22 11:11:16.265 Runtime[9809:522731] My girl

5.获取描述方法参数和返回值类型的字符串

const char * method_getTypeEncoding ( Method m );

实例:

    const char * types = method_getTypeEncoding(method);
    NSLog(@"%s",types);

输出:

2015-09-22 11:11:16.265 Runtime[9809:522731] v32@0:8@16@24

提示:这里的字符串,在我们后面介绍的动态创建类,给类添加方法是很好会用到。

6.获取方法的返回值类型的字符串

char * method_copyReturnType ( Method m );

实例:

     const char *returnType = method_copyReturnType(method);
     NSLog(@"%s",returnType);

输出:

2015-09-22 11:11:16.265 Runtime[9809:522731] v //v表示返回值为void类型

7.获取方法的指定位置参数的类型字符串

char * method_copyArgumentType ( Method m, unsigned int index );

实例:

 const char *indexType = method_copyArgumentType(method, 0);
 NSLog(@"%s",indexType);

输出:

2015-09-22 11:11:16.265 Runtime[9809:522731] @

注意:对象的方法(包括类方法)有两个隐藏参数,所左右这里method的索引可以到3,总共有4个参数,@表示参数是一个oc对象,:表示参数是一方法SEL。

8.通过引用返回方法的返回值类型字符串,同上面的method_copyReturnType一样

void method_getReturnType ( Method m, char *dst, size_t dst_len );

实例:

    char dst[1000];
    method_getReturnType(method, dst, sizeof(dst));
    NSLog(@"%s",dst);

输出:

2015-09-22 11:11:16.265 Runtime[9809:522731] v

9.返回方法的参数的个数

unsigned int method_getNumberOfArguments ( Method m );

实例:

    int index = method_getNumberOfArguments(method);
    NSLog(@"%d",index);

输出:

2015-09-22 11:11:16.265 Runtime[9809:522731] 4 //注意这里是4个参数哦

10.通过引用返回方法指定位置参数的类型字符串,与method_copyArgumentType一样

void method_getArgumentType ( Method m, unsigned int index, char *dst, size_t dst_len );

11.返回指定方法的方法描述结构体

struct objc_method_description * method_getDescription ( Method m );

实例:

    struct objc_method_description description = *method_getDescription(method);
    NSLog(@"SEL = %@,types = %s",NSStringFromSelector(description.name),description.types);

输出:

2015-09-22 11:11:16.265 Runtime[9809:522731] SEL = say:girl:,types = v32@0:8@16@24

12.设置方法的实现

IMP method_setImplementation ( Method m, IMP imp );

实例:
先定义一个代码块类型

typedef NSString* (^MyBlockType)(NSString *str0,NSString*str1);

在main.m里面添加

    MyBlockType block = ^(NSString *str0,NSString*str1){
        NSLog(@"str0 = %@",str0);
        NSLog(@"str1 = %@",str1);
        return @"10000";
    };
    IMP newIMP =
    imp_implementationWithBlock(block);

    IMP oldIMP = method_setImplementation(method, newIMP);
    if (oldIMP == imp) {
        NSLog(@"YES");//输出yes表示返回的IMP为旧的IMP
    }
    IMP currentIMP = method_getImplementation(method);
    currentIMP(@"第一参数1",nil,@"第二参数2",@"第三参数3");

输出:

2015-09-22 11:11:16.265 Runtime[9809:522731] YES
2015-09-22 11:11:16.265 Runtime[9809:522731] str0 = 第一参数1 
2015-09-22 11:11:16.265 Runtime[9809:522731] str1 = 第二参数2

总结:从输出结果我们可以看出当前Method的方法实现指向的是代码块。这里说明一下使用该方法可以交换任意两个Method的实现,不管两个Method的返回值类型,参数个数,类型是否一致。

13.交换两个方法的实现

void method_exchangeImplementations ( Method m1, Method m2 );

14.通过runtime调用类方法

-(CGFloat)cellHeight
{
    SEL cellHeightSEL = @selector(cellHeightWithData:);
    Method method = class_getClassMethod(self.cellClass, cellHeightSEL);
    //定义一个函数类型来保存IMP
    typedef CGFloat (*heightForCellFunc)(Class, SEL, id);
    heightForCellFunc func =  (heightForCellFunc)method_getImplementation(method);
    return func(self.cellClass,cellHeightSEL,self);
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值