RunTime运行时(二)

转自原文地址:http://blog.csdn.net/wzzvictory/article/details/8624057

今天开始说说runtime system中最关键的消息相关内容。

一、runtime中的消息

  • 1、什么是消息

进入今天的正题之前,先来说说跟message息息相关的几个概念

①message(消息)

message的具体定义很难说,因为并没有真正的代码描述,简单的讲message 是一种抽象,包括了函数名+参数列表,他并没有实际的实体存在。

②method(方法)

method是真正的存在的代码。如:- (int)meaning { return 42; }

③selector(方法选择器)

selector通过SEL类型存在,描述一个特定的method 或者说 message。在实际编程中,可以通过selector进行检索方法等操作。

  • 2、两个跟消息相关的概念

①SEL

SEL又叫方法选择器,这到底是个什么玩意呢?在objc.h中是这样定义的:

typedef struct objc_selector *SEL;   

这个SEL表示什么?首先,说白了,方法选择器仅仅是一个char *指针,仅仅表示它所代表的方法名字罢了,有如下证据:

SEL selector = @selector(message); //@selector不是函数调用,只是给这个坑爹的编译器的一个提示   
NSLog (@"%s", (char *)selector); //print message

这时打印的结果就是:message

Objective-C在编译的时候,会根据方法的名字,生成一个用 来区分这个方法的唯一的一个ID,这个ID就是SEL类型的。我们需要注意的是,只要方法的名字相同,那么它们的ID都是相同的。就是说,不管是超类还是子类,不管是有没有超类和子类的关系,只要名字相同那么ID就是一样的。而这也就导致了Objective-C在处理有相同函数名和参数个数但参数类型不同的函数的能力非常的弱,比如当你想在程序中实现下面两个方法:

-(void)setWidth:(int)width;   
-(void)setWidth:(double)width;   

这样的函数则被认为是一种编译错误,而这最终导致了一个非常非常奇怪的Objective-C特色的函数命名:

-(void)setWidthIntValue:(int)width;   
-(void)setWidthDoubleValue:(double)width;  

可能有人会问,runtime费了那么老半天劲,究竟想做什么?GC来了。

刚才我们说道,编译器会根据每个方法的方法名为那个方法生成唯一的SEL,这些SEL组成了一个Set集合,这个Set简单的说就是一个经过了优化过的hash表。而Set的特点就是唯一,也就是SEL是唯一的,因此,如果我们想到这个方法集合中查找某个方法时,只需要去找到这个方法对应的SEL就行了,SEL实际上就是根据方法名hash化了的一个字符串,而对于字符串的比较仅仅需要比较他们的地址就可以了,犀利,速度上无语伦比!!但是,有一个问题,就是数量增多会增大hash冲突而导致的性能下降(或是没有冲突,因为也可能用的是perfect hash)。但是不管使用什么样的方法加速,如果能够将总量减少(多个方法可能对应同一个SEL),那将是最犀利的方法。那么,我们就不难理解,为什么SEL仅仅是函数名了。

到这里,我们明白了,本质上,SEL只是一个指向方法的指针(准确的说,只是一个根据方法名hash化了的KEY值,能唯一代表一个方法),它的存在只是为了加快方法的查询速度!!!!

  • ②IMP

IMP在objc.h中是如此定义的:

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

这个比SEL要好理解多了,熟悉C语言的同学都知道,这其实是一个函数指针。前面介绍过的SEL,就是为IMP服务的。由于每个方法都对应唯一的SEL,因此我们可以通过SEL方便、快速、准确的获得它所对应的IMP(也就是函数指针),而在取得了函数指针之后,也就意味着我们取得了执行的时候的这段方法的代码的入口,这样我们就可以像普通的C语言函数调用一样使用这个函数指针。当然我们可以把函数指针作为参数传递到其他的方法,或者实例变量里面,从而获得极大的动态性。

下面的例子,介绍了取得函数指针,即函数指针的用法:

void (* performMessage)(id,SEL);//定义一个IMP(函数指针)   
performMessage = (void (*)(id,SEL))[self methodForSelector:@selector(message)];//通过methodForSelector方法根据SEL获取对应的函数指针   
performMessage(self,@selector(message));//通过取到的IMP(函数指针)跳过runtime消息传递机制,直接执行message方法 

用IMP 的方式,省去了runtime消息传递过程中所做的一系列动作,比直接向对象发送消息高效一些。

  • 3、传递消息所用的几个runtime方法

上篇文章中我们说过,下面的方法:

[receiver message]   
在编译后会变成:
[cpp] view plain copy
objc_msgSend(receiver, selector)   
实际上,同objc_msgSend方法类似的还有几个:
[cpp] view plain copy
objc_msgSend_stret(返回值是结构体)  
objc_msgSend_fpret(返回值是浮点型)  
objc_msgSendSuper(调用父类方法)  
objc_msgSendSuper_stret(调用父类方法,返回值是结构体) 

它们的作用都是类似的,为了简单起见,后续介绍消息和消息传递机制都以objc_msgSend方法为例。

二、消息调用流程

一切还是从消息表达式[receiver message]开始,在被转换成objc_msgSend(receiver, SEL)后,在运行时,runtime system会做以下事情:

  • 1、检查忽略的Selector,比如当我们运行在有垃圾回收机制的环境中,将会忽略retain和release消息。

  • 2、检查receiver是否为nil。不像其他语言,nil在objective-C中是完全合法的,并且这里有很多原因你也愿意这样,比如,至少我们省去了给一个对象发送消息前检查对象是否为空的操作。如果receiver为空,则会将 selector也设置为空,并且直接返回到消息调用的地方。如果对象非空,就继续下一步。

  • 3、接下来会根据SEL到当前类中查找对应的IMP,首先会在cache中检索它,如果找到了就根据函数指针跳转到这个函数执行,否则进行下一步。

  • 4、检索当前类对象中的方法表(method list),如果找到了,加入cache中,并且就跳转到这个函数之行,否则进行下一步。

  • 5、从父类中寻找,直到根类:NSObject类。找到了就将方法加入对应类的cache表中,如果仍为找到,则要进入后文介绍的内容:动态方法决议。

  • 6、如果动态方法决议仍不能解决问题,只能进行最后一次尝试,进入消息转发流程。

  • 7、如果还不行,去死吧。

下面的图部分展示了这个调用过程:


写到这大家肯定会发出这样的疑问:我仅仅想调用一个方法而已,却不得不经历那么多步骤,效率上怎么保证??苹果也做了一些优化上的工作。

三、函数检索优化措施

主要从下面两个方面着手:

  • 1、通过SEL进行IMP匹配

先来看看类对象中保存的方法列表和方法的数据结构:

typedef struct method_list_t {  
    uint32_t entsize_NEVER_USE;    
    uint32_t count;  
    struct method_t first;  
} method_list_t;  
  
typedef struct method_t {  
    SEL name;  
    const char *types;//参数类型和返回值类型  
    IMP imp;  
} method_t;  

在前一篇文章介绍SEL的时候,我们已经说过了苹果在通过SEL检索IMP时做的努力,这里不再累述。

  • 2、cache缓存

cache的原则就是缓存那些可能要执行的函数地址,那么下次调用的时候,速度就可以快速很多。这个和CPU的各种缓存原理相通。好吧,说了这么多了,再来认识几个名词:

struct objc_cache {  
    uintptr_t mask;              
    uintptr_t occupied;          
    cache_entry *buckets[1];  
};  
  
typedef struct {  
    SEL name;       
    void *unused;  
    IMP imp;    
} cache_entry;  

看这个结构,有没有搞错又是hash table。

objc_msgSend 首先在cache list 中找SEL,没有找到就在class method中找,super class method中找(当然super class 也有cache list)。而cache的机制则非常复杂了,由于Objective-C是动态语言。所以,这里面还有很多的多线程同步问题,而这些锁又是效率的大敌,相关的内容已经远远超过本文讨论的范围。如果在缓存中已经有了需要的方法选标,则消息仅仅比函数调用慢一点点。如果程序运行了足够长的时间,几乎每个消息都能在缓存中找到方法实现。程序运行时,缓存也将随着新的消息的增加而增加。据牛人说(没有亲测过),苹果通过这些优化,使消息传递和直接的函数调用效率上的差距已经相当的小。

四、方法调用中的隐藏参数

亲爱的Objective-C程序员们,你们在进行面向对象编程的时候,在实例方法中都是用过self关键字吧,可是你有没有想过,为什么在一个实例方法中,通过self关键字就能取到调用当前方法的对象呢?这就要归功与runtime system消息的隐藏参数了。(注:在此修正,类方法和实例方法中,都可以访问self和_cmd这两个属性,因为它们都不属于类的实例变量,而是形参!!!!误导大家了,深表歉意!!!!)

当objc_msgSend找到方法对应的实现时,它将直接调用该方法实现,并将消息中所有的参数都传递给方法实现,同时,它还将传递两个隐藏的参数:

接收消息的对象(也就是self指向的内容)

方法选标(_cmd指向的内容)

这些参数帮助方法实现获得了消息表达式的信息。它们被认为是”隐藏“的是因为它们并没有在定义方法的源代码中声明,而是在代码编译时是插入方法的实现中的。尽管这些参数没有被显示声明,但在源代码中仍然可以引用它们(就象可以引用消息接收者对象的实例变 量一样)。在方法中可以通过 self 来引用消息接收者对象,通过选标_cmd 来引用方法本身。下面的例子很好的说明了这个问题:

- (void)message  
{  
    self.name = @"James";//通过self关键字给当前对象的属性赋值  
    SEL currentSel = _cmd;//通过_cmd关键字取到当前函数对应的SEL  
    NSLog(@"currentSel is :%s",(char *)currentSel);  
}  

打印结果:

ObjcRunTime[693:403] currentSel is :message  

当然,在这两个参数中,self 更有用,更常用一些。实际上,它是在方法实现中访问消息接收者对象的实例变量的途径。

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

推荐阅读更多精彩内容

  • 转至元数据结尾创建: 董潇伟,最新修改于: 十二月 23, 2016 转至元数据起始第一章:isa和Class一....
    40c0490e5268阅读 1,635评论 0 9
  • 参考链接: http://www.cnblogs.com/ioshe/p/5489086.html 简介 Runt...
    乐乐的简书阅读 2,097评论 0 9
  • 参数自一个指针,指向类的要接收消息的实例。 OP在处理该信息的方法的选择。 ......可变参数列表包含参数的方法...
    reallychao阅读 720评论 0 0
  • 转载:http://yulingtianxia.com/blog/2014/11/05/objective-c-r...
    F麦子阅读 694评论 0 2
  • 这篇文章完全是基于南峰子老师博客的转载 这篇文章完全是基于南峰子老师博客的转载 这篇文章完全是基于南峰子老师博客的...
    西木阅读 30,469评论 33 467