runtime 笔记(方法调用-消息传递过程)

什么是runtime?

Objective-C 是一门动态语言,相比C语言,是在其基础上增加了面向对象的特性和消息传递机制。而面向对象的特性和消息传递机制实现的关键就是runtime,也就是常说的运行时机制。

当我们平时使用OC去调用一个方法时,其实是在运行时通过runtime给对象发消息。那么这里的发消息和我们C语言里去调用一个函数有什么区别呢?在C语言里我们去调用一个方法(函数),其实是就是去调用内存中的一段代码,这里的函数名其实是和内存中的代码段绑定在一起的,而且这个绑定过程在编译阶段就已经确定下来了。而在OC中我们去调用一个对象的方法,并不会立即去执行这个方法,而是会先向这个对象发送一个消息,而具体调用的代码在这个时候是不确定的,因为最后调用的方法可能会是这个对象的其它的方法,也有可能是其他对象的方法。在这期间决定最后调用的方法的过程就是消息传递的过程。

runtime的消息传递过程

在了解消息传递过程之前,我们要先知道接收消息的对象本质是什么,我们看一下objc.h中objc_object的定义

#if !OBJC_TYPES_DEFINED
/// An opaque type that represents an Objective-C class.
typedef struct objc_class *Class;

/// Represents an instance of a class.
struct objc_object {
    Class _Nonnull isa  OBJC_ISA_AVAILABILITY;
};

/// A pointer to an instance of a class.
typedef struct objc_object *id;
#endif

看上面的代码我们知道我们创建的对象其实是一个struct objc_object 结构体,我们平常使用的id其实也是这个结构体,这个结构体中只有一个变量,一个Class类型的变量,这个变量也是一个struct objc_class结构体。

在消息传递的过程中,runtime会先通过object的isa指针来找到这个对象所属的类,也就是上面的struct objc_class,然后在这个类对象的方法列表中去寻找。这里我们先了解一下struct objc_class里有哪些东西:

struct objc_class {
    Class _Nonnull isa  OBJC_ISA_AVAILABILITY;

#if !__OBJC2__
    Class _Nullable super_class                              OBJC2_UNAVAILABLE;
    const char * _Nonnull name                               OBJC2_UNAVAILABLE;
    long version                                             OBJC2_UNAVAILABLE;
    long info                                                OBJC2_UNAVAILABLE;
    long instance_size                                       OBJC2_UNAVAILABLE;
    struct objc_ivar_list * _Nullable ivars                  OBJC2_UNAVAILABLE;
    struct objc_method_list * _Nullable * _Nullable methodLists                    OBJC2_UNAVAILABLE;
    struct objc_cache * _Nonnull cache                       OBJC2_UNAVAILABLE;
    struct objc_protocol_list * _Nullable protocols          OBJC2_UNAVAILABLE;
#endif

} OBJC2_UNAVAILABLE;

上面代码中我们可以看到有cachemethodLists两个参数,而查找方法也是在这两个变量中去找的。为了优化性能和缩短方法的查找时间,objc_classcache结构体中存储的是每一次类或者实例对象调用的方法。

1.当类或者实例对象调用方法时,会优先在cache中寻找相应的方法,如果找到了,则直接去找methodIMP实现并执行。如果找不到那就methodLists中去遍历查找,如果在methodLists找到了则会执行该方法IMP,并且把methodmethod_name作为key,methodIMP作为value保存到objc_cache中。

PS:这里说一下IMP是什么,IMP函数指针指向了方法实现的首地址,当 Objective-C发起消息时,最终执行的方法是由IMP决定的。

2.如果在methodLists中没有找到方法,则会去根据super_class获取到当前类的父类,重复1中的查找(如果是类方法则去找该类的元类)。如果找到了则返回,否则继续向上查找。

3.如果找到NSObject类还没有找到,则会进入消息转发阶段。

消息转发:

1.动态方法解析

动态解析,当没有找到方法时,runtime为我们提供了一次动态添加方法实现的机会,让你去提供一个函数的实现。如果你实现了这两个方法并添加了新的方法,那么runtime会重新启动一次消息发送过程。

具体实现主要通过下面的三个方法

// 实现这个方法来添加新的类方法
+ (BOOL)resolveClassMethod:(SEL)sel;

// 实现这个方法来添加新的对象方法
+ (BOOL)resolveInstanceMethod:(SEL)sel;

/**
 运行时方法:向指定类中添加特定方法实现的操作
 @param cls 被添加方法的所属的类
 @param name 被添加方法的selector方法名
 @param imp 被添加方法的实现
 @param types 被添加方法的返回值与参数类型
 @return 添加方法成功还是失败
 */
BOOL class_addMethod(Class _Nullable cls,
                     SEL _Nonnull name,
                     IMP _Nonnull imp,
                     const char * _Nullable types)
SEL:SEL是表示一个方法的selector指针,OC会根据不同方法的名字和参数序列,生成唯一的标识,也就是这个指针的地址,所以就不难理解为什么OC中不能定义方法名相同但参数不同的方法了。

2.消息接受者重定向

-(id)forwardingTargetForSelector:(SEL)aSelector;

如果你没有在动态方法解析时添加方法,那接下来runtime会检查你是否实现了(id)forwardingTargetForSelector:(SEL)aSelector方法,如果实现了该方法则会将消息转发给你返回的对象,重复上面的消息查找过程。

3.消息重定向

// 通过方法调用者创建方法签名,然后将这个签名通过NSInvocation封装成方法调用传给重定向方法
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector;
// 拿到传过来的invocation参数,再次进行IMP查找
- (void)forwardInvocation:(NSInvocation *)anInvocation;

如果前面的两次机会都没有挽回,这时runtime会启动完整的消息转发机制

1.先发送- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector消息,拿到方法的参数和返回值信息,如果实现了该方法,并返回一个方法签名,runtime就会创建一个 NSInvocation对象并发送给- (void)forwardInvocation:(NSInvocation *)anInvocation方法通知该对象,给予此次消息发送的最后一次寻找IMP的机会。
2.如果没有实现该方法或返回值为nil,那么runtime则会发出doesNotRecognizeSelector消息,控制台输出unrecognized selector sent to instance同时直接Crash掉。

至此整个消息传递过程就结束了,这里总结一下:
1.我们创建的对象是一个objc_object结构体
2.调用方法时其实是通过runtime给对象发消息
3.消息的传递过程是根据结构体的Class参数去寻找所属类,并去cachemethodLists里去查找(没找到的话则根据super_class去父类里找,直到查找到NSObject)。
4.如果还没找到则开始进行 消息转发机制(消息动态解析,消息接受者重定向,消息重定向)

至此,我们对runtime的整个消息传递应该有个轮廓了,希望能帮助到大家

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

推荐阅读更多精彩内容

  • 我们常常会听说 Objective-C 是一门动态语言,那么这个「动态」表现在哪呢?我想最主要的表现就是 Obje...
    Ethan_Struggle阅读 2,107评论 0 7
  • 前言 runtime其实在我们日常开发过程中很少使用到,尤其是像我现在比较初级的程序猿就更用不到了。但是去面试很多...
    WolfTin阅读 568评论 0 2
  • 本文转载自:http://yulingtianxia.com/blog/2014/11/05/objective-...
    ant_flex阅读 719评论 0 1
  • 本文详细整理了 Cocoa 的 Runtime 系统的知识,它使得 Objective-C 如虎添翼,具备了灵活的...
    lylaut阅读 770评论 0 4
  • 文中的实验代码我放在了这个项目中。 以下内容是我通过整理[这篇博客] (http://yulingtianxia....
    茗涙阅读 876评论 0 6