Runtime 方法与消息

基础数据类型
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指针也会被初始化,让对象可以访问类和类的继承体系

一个消息的基本框架:


runtime 消息基本框架.gif

当消息发送给一个对象时,objc_msgSend通过对象的isa指针获取到类的结构体,然后在方法分发表中查找方法的selector,如果没有找到selector,则通过objc_msgSend结构体中指向父类的指针找到其父类,并在父类的分发表中查找方法的selector。以此,会一直沿着类的继承体系到达NSObject类。定位到selector,函数就会获取了实现的入口点,并传入相应的参数来执行方法的具体实现。如果没有定位到selector,则走消息转发流程。

为了加速消息的处理,运行时系统缓存使用过的selector和对应的方法的地址。

隐藏参数

objc_msgSend有两个隐藏参数
1.消息接受对象
2.方法的selector
这两个参数为方法的实现提供了调用者的消息。是在编译期被插入实现代码的

获取方法地址

Runtime 中方法的动态绑定让我们写代码时更具灵活性,如可以把消息转发给我们想要的对象,或者随意交换一个方法的实现等。灵活性的提升带来性能上的损耗。

当我们需要在一个循环内频繁调用一个特定的方法时,通过获取方法实现的地址,像调用函数一样来直接调用它。可以提高程序的性能

NSObject类提供了methodForSelector:方法,可以获取方法的指针,然后通过这个文件夹

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 219,539评论 6 508
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,594评论 3 396
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 165,871评论 0 356
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,963评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,984评论 6 393
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,763评论 1 307
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,468评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,357评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,850评论 1 317
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,002评论 3 338
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,144评论 1 351
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,823评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,483评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,026评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,150评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,415评论 3 373
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,092评论 2 355

推荐阅读更多精彩内容