基础数据类型
SEL
sel又叫选择器,是表示一个方法的selector的指针,其定义如下:
typedef struct objc_selector *SEL;
objc_selector结构体的详细定义没有在<objc/runtime.h>头文件中找到。方法的selector用于表示运行时方法的名字。Objective-C在编译时,会根据每一个方法的名字、参数序列,生成一个唯一的整数标识(int类型的指针),这个标识就是SEL。如下代码:
SEL sel = @selector(method);
NSLog(@"sel : %p", sell);
sell以一串16进制地址格式输出
两个类之间,不管它们是父类与子类的关系,还是之间没有这种关系,只要方法名相同,那么方法的SEL就是一样的。每一个方法都对应着一个个SEL。所以0C在同一个类(和类的继承体系)中。不能存在两个同名的方法,即使参数类型不同也不行。相同的方法只能对应一个SEL。这也就导致OC在处理相同方法名且参数个数不同的方法方面的能力很差。如:
- (void)setWidth:(int)width;
- (void)setWidth:(double)width;
这样的定义呗认为是一种编译错误
当然,不同的类可以拥有相同的selector,这个没问题。不同类的实例对象执行相同的selector时,会在各自的方法列表中去根据selector去寻找自己对应的IMP.
工程中所有的sel组成一个set集合,set的特点就是唯一,因此sel是唯一的。因此,如果我们想到这个方法集合中查找某个方法时,只需要去找到这个方法对应的sel就行了,sel实际上就是根据方法名hash化了的一个个字符串,而对于字符串的比较仅仅需要比较它们的地址就行了,速度上很快。但是,有一个问题,就是数量增多会增大hash冲突而导致的性能下降(或是没有冲突,因为也可能用perfect hash)。但无论使用什么方法加速,如果可以将总量减少(多个方法对应同一个sel),那就很厉害。这样,就不难理解,为什么sel仅仅是函数名了。
本质上,sel只是一个指向方法的指针(准确的说,只是一个根据方法名hash化了的key值,能唯一代表一个方法),它的存在只是为了加快方法的查询速度。
我们可以在运行时添加新的selector,也可以在运行时获取已经存在的selector,通过下列三种方法获取sel:
1.sel_registerName函数
2.Objective-C编译器提供的@selector()
3.NSSelectorFromString()方法
IMP
imp实际上是一个个函数指针,指向方法实现的首地址。定义如下:
id (*IMP)(id, SEL,...)
这个函数使用当前CPU架构实现的标准的c调用约定。第一个参数是指向self的指针(如果是实例方法,则是类实例的内存地址;如果是类方法,则是指向元类的指针),第二个参数是方法选择器,接下来是方法的实际参数列表
前面介绍的SEL就是为了查找方法的最终实现IMP的。由于每一个方法对应唯一的SEL,、因此我们可以通过SEL方便快速准确获得它所对应的IMP,获得IMP后,我们就获得了执行这个方法代码的入口点,就可以像调用普通c函数一样使用
通过取得IMP,我们可以跳过runtime消息传递机制,直接执行IMP指向的函数实现,这样省去了runtime消息传递过程中所做的一系列查找操作,会比直接向对象发送消息高效一些。
Method
介绍完SEL 和IMP,可以来讲下Method。Method用于表示类定义中的方法,定义如下:
typedef struct objc_method *Method;
struct objc_method {
SEL method_name;
char *method_types;
IMP method_imp;
}
该结构体中包含了一个SEL和IMP,实际上相当于在SEL和IMP之间作了一个映射。有了SEL,便可以找到对应的IMP,从而调用方法的实现代码。具体流程:
objc_method_description
objc_merhod_description定义了一个OC方法:
struct objc_method_description {SEL name; char *types};
方法相关操作函数
runtime提供了一系列的方法来处理与方法相关的操作。包括方法本身和sel。
方法:
方法相关函数包括:
id method_invoke(id receiver, Method m, ...); //调用指定方法的实现
void method_invoke_stret (id receiver, Method m, ...) //调用返回一个数据结构的方法的实现
SEL method_getName (Method m); //获取方法名
IMP method_getImplementation (Method m); //返回方法的实现
const char * method_getTypeEncoding (Method m); //获取描述方法参数和返回值类型的字符串
char * method_copyReturnType (Method m); //获取方法的返回值类型的字符串
char * method_copyArgumentType (Method m, unsigned int index); //获取方法的制定位置参数的类型字符串
void method_getReturnType (Method m, char *dst, size_t dat_len); //通过引用返回方法的返回值类型字符串
unsigned int method_getNumberOfArguments (Method m); //返回方法的参数的个数
void method_getArgumentType (Method m, unsigned int index, char *dst, size_t dat_len); //通过引用返回方法指定位置参数的类型字符串
struct objc_method_description * method)getDescription (Method m); //返回指定方法的方法描述结构体
IMP method_setImplemetation (Method m, IMP imp); //设置方法的实现
void method_exchangeImplemetations (Method m1, Method m2); //交换两个方法的实现
method_invoke函数,返回的是实际实现的返回值。参数receiver不能为空。这个方法的效率会比method_getImplementation和method_getName更快。
method_getName函数,返回的是一个SEL。如果想获取方法名的C字符串,可以使用sel_getName(method_getName(method))。
method_getReturnType函数,类型字符串会被拷贝到dst中。
method_setImplementation函数,注意该函数返回值是方法之前的实现
方法选择器
const char * sel_getName (SEL sel); //返回给定选择器指定的方法的名称
SEL sel_registerName (const char *str); //在oc runtime系统中注册一个方法,将方法名映射到一个选择器,并返回这个选择器
SEL sel_getUid (const char *str);
BOOL sel_isEqual (SEL ohs, SEL rhs); //比较两个选择器
sel_registerName函数:在我们将一个方法添加到类定义时,我们必须在Objective-C Runtime系统中注册一个方法名以获取方法的选择器
方法调用流程
在oc中,消息直到运行时才绑定到方法实现上。编译器会将消息表达式[receiver message]转化为一个消息函数的调用, 即objc_msgSend.这个函数将消息接受者和方法名作为其基础参数,如以下所示:
objc_msgSend (receiver, selector)
如果消息中还有其它参数。如下:
objc_msgSend (receiver, selector, arg1, arg2, ...)
这个函数完成了动态绑定的所有事情:
1.首先它找到了selector对应的方法实现。因为同一个方法可能在不同的类中有不同的实现。所以需要依赖接受者的类来找到确切的实现 。
2.它调用方法实现,并将接受者对象和方法的所有参数传给它。
3.最后,它将实现返回的值作为它自己的返回值。
消息的关键在于结构体objc_class,这个结构体有两个字段是在分发消息的关注的:
1.指向父类的指针
2.一个类方法分发表。即methodlists
当创建新对象时,先为其分配内存,并初始化成员变量。其中isa指针也会被初始化,让对象可以访问类和类的继承体系
一个消息的基本框架:
当消息发送给一个对象时,objc_msgSend通过对象的isa指针获取到类的结构体,然后在方法分发表中查找方法的selector,如果没有找到selector,则通过objc_msgSend结构体中指向父类的指针找到其父类,并在父类的分发表中查找方法的selector。以此,会一直沿着类的继承体系到达NSObject类。定位到selector,函数就会获取了实现的入口点,并传入相应的参数来执行方法的具体实现。如果没有定位到selector,则走消息转发流程。
为了加速消息的处理,运行时系统缓存使用过的selector和对应的方法的地址。
隐藏参数
objc_msgSend有两个隐藏参数
1.消息接受对象
2.方法的selector
这两个参数为方法的实现提供了调用者的消息。是在编译期被插入实现代码的
获取方法地址
Runtime 中方法的动态绑定让我们写代码时更具灵活性,如可以把消息转发给我们想要的对象,或者随意交换一个方法的实现等。灵活性的提升带来性能上的损耗。
当我们需要在一个循环内频繁调用一个特定的方法时,通过获取方法实现的地址,像调用函数一样来直接调用它。可以提高程序的性能
NSObject类提供了methodForSelector:方法,可以获取方法的指针,然后通过这个文件夹