OC的Runtime机制之消息

ObjC的方法调用都是动态的,这点和其他的语言是有区别的,为了更深层次理解动态的概念,我们必须先知道 Class,SEL,IMP这三个概念。

typedef struct objc_class *Class; typedef struct objc_object { 
Class isa; } *id; 
typedef struct objc_selector *SEL; typedef id (*IMP)(id, SEL, ...); 

class 的含义我们之前讲个,他被定义为一个指向obic_class的结构体指针,这个结构体的定义如下

struct objc_class {
struct objc_class* isa;struct objc_class super_class; /*父类*/ 
const char *name; /*类名字*/ long version; /*版本信息*/ 
long info; /*类信息*/long instance_size; /*实例大小*/struct objc_ivar_list *ivars; /*实例参数链表*/ 
struct objc_method_list **methodLists; /*方法链表*/ 
struct objc_cache *cache; /*方法缓存*/ 
struct objc_protocol_list *protocols; /*协议链表*/ 
}

有此可见,Class是指向类结构体的指针,改结构体含有一个指向其父类类结构的指针,该类的名称,实例大小等其他信息。
NSObject 的 class 方法就返回这样一个指向其类结构的指针。每一个类实例对象的第一个实例变量是一 个指向该对象的类结构指针,叫做isa,通过isa,对象可以访问它对应的类,以及父类。实例对象的第一个实例变量为 isa,它指向该类的类结构 The object’s class。 而该类结构有一个指向其父类类结构的指针 superclass, 以及自身消息名称(selector)/实现地址(address) 的方法链表。如下图

messaging1.gif

方法的含义:
这里所说的方法链表里面储存的是Method类型的。图中selector就是指Method的SEL,address就是address 就是指 Method 的 IMP 。
Method 的结构如下

struct objc_method {
    SEL method_name                                          OBJC2_UNAVAILABLE;
    char *method_types                                       OBJC2_UNAVAILABLE;
    IMP method_imp                                           OBJC2_UNAVAILABLE;
}

一个方法 Method,其包含一个方法选标 SEL – 表示该方法的名称,一个 types – 表示该方法参数的类型, 一个 IMP - 指向该方法的具体实现的函数指针。
SEL的含义:
SEL的定义为:typedef struct objc_selector *SEL;
不同的类可以拥有相同的 selector,这个没有问题,因为不同类的实例对象 performSelector 相同的 selector 时,会在各自的消息选标(selector)/实现地址(address) 方法链表中根据 selector 去查找具体的 方法实现 IMP, 然后用这个方法实现去执行具体的实现代码。这是一个动态绑定的过程,在编译的时候, 我们不知道最终会执行哪一些代码,只有在执行的时候,通过 selector 去查询,我们才能确定具体的执行 代码。
方法列表的定义

//方法列表
struct objc_method_list {
    struct objc_method_list *obsolete                        OBJC2_UNAVAILABLE;

    int method_count                                         OBJC2_UNAVAILABLE;
#ifdef __LP64__
    int space                                                OBJC2_UNAVAILABLE;
#endif
    /* variable length structure */
    struct objc_method method_list[1]                        OBJC2_UNAVAILABLE;
}  

IMP 的含义:在前面我们也看到 IMP 的定义为: typedef id (*IMP)(id, SEL, ...);
IMP 是一个函数指针,这个被指向的函数包含一个接收消息的 对象 id(self 指针), 调用方法的选标 SEL (方法名),以及不定个数的方法参数,并返回一个 id。也就是说 IMP 是消息最终调用的执行代码,是方法真正的实现代码 。我们可以像在C语言里面一样使用这个函数 指针。
NSObject 类中的 methodForSelector:方法就是这样一个获取指向方法实现 IMP 的指针, methodForSelector:返回的指针和赋值的变量类型必须完全一致,包括方法的参数类型和返回值类型。
使用 methodForSelector:来避免动态绑定将减少大部分消息的开销,但是这只有在指定的消息被重复发 送很多次时才有意义,例如上面的 for 循环。
注意,methodForSelector:是 Cocoa 运行时系统的提供的功能,而不是 Objective-C 语言本身的功能。

下面我们总结一下:
1.一个实例对象的第一个实例变量是isa,这个isa指向该对象的类结构,类结构有指向父类类结构。
2.在元类中是储存类方法的,方法链表里储存的是Mehtod类型的。SEL代表改方法的名称,IMP代表该方法具体实现的函数指针。
3.当实例对象查找方法时,会在方法列表中根据SEL去查找具体的实现的IMP,根据IMP这个指针然后找到具体实现的代码,这个是一个动态绑定的过程。
消息调用过程:
先举出一个例子

Cat * cat = [[Cat alloc] init]; 
[cat eat]; 

消息函数 obj_msgSend:当调用方法的时候,编译器会将消息转换为objc_msgSend 的调用,两个参数,消息接收者id和消息对应的方法名称SEL,同时接收消息的任何参数。
id objc_msgSend(id theReceiver, SELtheSelector, ...)
如上:
objc_msgSend(cat,@selector(eat) );
该消息做了动态绑定的所需要的一切工作:
1,它首先找到 SEL 对应的方法实现 IMP。因为不同的类对同一方法可能会有不同的实现,所以找到的 方法实现依赖于消息接收者的类型。2, 然后将消息接收者对象(指向消息接收者对象的指针)以及方法中指定的参数传递给方法实现 IMP。
3, 最后,将方法实现的返回值作为该函数的返回值返回。

查找IMP的过程
我们在前面的类结构中也看到有一个叫 objc_cache *cache 的成员,这个缓存为提高效率而存在 的。每个类都有一个独立的缓存,同时包括继承的方法和在该类中定义的方法。。
查找过程:
当调用[cat eat]; 这个方法的时候,编译器会将其转换成objc_msgSend(cat,@selector(eat) );这个方法做了动态绑定的一切工作
1,首先根据SEL去该类的方法 cache 中查找,如果找到了调到6;
2,如果没有找到,就去该类的方法列表中查找。如果在该类的方法列表中找到了,跳到6,并将 它加入 cache 中缓存起来。根据最近使用原则,这个方法再次调用的可能性很大,缓存起来可以节省下次 调用再次查找的开销。
3,如果在该类的方法列表中没找到对应的 IMP,在通过该类结构中的 super_class 指针在其父类结构的方法列表中去查找,直到在某个父类的方法列表中找到对应的 IMP,返回它,并加入 cache 中;
4,如果在自身以及所有父类的方法列表中都没有找到对应的 IMP,则看是不是可以进行动态方法决议
5,如果动态方法决议没能解决问题,进入消息转发流程。
6,它首先找到 SEL 对应的方法实现 IMP。因为不同的类对同一方法可能会有不同的实现,所以找到的 方法实现依赖于消息接收者的类型。7, 然后将消息接收者对象(指向消息接收者对象的指针)以及方法中指定的参数传递给方法实现 IMP。
8, 最后,将方法实现的返回值作为该函数的返回值返回。
我们可以通过 NSObject 的一些方法获取运行时信息或动态执行一些消息:
class 返回对象的类;isKindOfClass 和 isMemberOfClass 检查对象是否在指定的类继承体系中;
respondsToSelector 检查对象能否指定相应的消息;conformsToProtocol 检查对象是否实现了指定协议类的方法;
methodForSelector 返回指定方法实现的地址。
performSelector:withObject 执行 SEL 所指代的方法。

消息转发:
通常,给一个对象发送它不能处理的消息会得到出错提示,然而,Objective-C 运行时系统在抛出错误之前, 会给消息接收对象发送一条特别的消息 forwardInvocation 来通知该对象,该消息的唯一参数是个 NSInvocation 类型的对象——该对象封装了原始的消息和消息的参数。
我们可以实现 forwardInvocation:方法来对不能处理的消息做一些默认的处理,也可以将消息转发给其他对 象来处理,而不抛出错误。
forwardInvocation:消息给这个问题提供了一个更特别的,动态的解决方案:当一个对象由于没有相应的方 法实现而无法响应某消息时,运行时系统将通过 forwardInvocation:消息通知该对象。每个对象都从 NSObject 类中继承了 forwardInvocation:方法。然而,NSObject 中的方法实现只是简单地调用了 doesNotRecognizeSelector:。通过实现我们自己的 forwardInvocation:方法,我们可以在该方法实现中将 消息转发给其它对象。
要转发消息给其它对象,forwardInvocation:方法所必须做的有:
1,决定将消息转发给谁,并且 2,将消息和原来的参数一块转发出去。

- (void) forwardInvocation:(NSInvocation *)anInvocation { 
if ([someOtherObject respondsToSelector:[anInvocation selector]]) 
[anInvocation invokeWithTarget:someOtherObject]; 
else[super forwardInvocation:anInvocation]; 
} 

转发消息后的返回值将返回给原来的消息发送者。您可以将返回任何类型的返回值,包括: id,结构体,浮 点数等。
forwardInvocation:方法就像一个不能识别的消息的分发中心,将这些消息转发给不同接收对象。或者它也 可以象一个运输站将所有的消息都发送给同一个接收对象。它可以将一个消息翻译成另外一个消息,或者 简单的"吃掉―某些消息,因此没有响应也没有错误。forwardInvocation:方法也可以对不同的消息提供同样 的响应,这一切都取决于方法的具体实现。该方法所提供是将不同的对象链接到消息链的能力。
注意: forwardInvocation:方法只有在消息接收对象中无法正常响应消息时才会被调用。 所以,如果我们 希望一个对象将 negotiate 消息转发给其它对象,则这个对象不能有 negotiate 方法,也不能在动态方法 决议过程中为之提供实现。否则,forwardInvocation:将不可能会被调用。

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

推荐阅读更多精彩内容