iOS 中的runtime与消息转发

在80年代初,小李和小王是异地恋的情侣,小王在改革号角的引领下毅然选择了南方的一个城市去奋斗,而那个时候没有手机,他们之间的互诉相思的方式主要依靠写信。但是由于小王又经常出差,居住地址会经常变动。所以小李每次给小王的回信,小王可能因为地址的变动而没有收到,他们后来想到了一个好办法来解决这个问题,具体的方法如下:

80年代的消息转发


其实上面这张图,基本上就可以表达Runtime在iOS中的作用以及iOS的消息转发机制。Runtime的特性主要是消息(方法)传递,如果消息(方法)在对象中找不到,就进行转发,具体怎么实现的呢。我们从下面几个方面探寻Runtime的实现机制。

Runtime介绍

Objective-C 扩展了 C 语言,并加入了面向对象特性和 Smalltalk 式的消息传递机制。而这个扩展的核心是一个用 C 和 编译语言 写的 Runtime 库。它是 Objective-C 面向对象和动态机制的基石。

Objective-C 是一个动态语言,这意味着它不仅需要一个编译器,也需要一个运行时系统来动态得创建类和对象、进行消息传递和转发。理解 Objective-C 的 Runtime 机制可以帮我们更好的了解这个语言,适当的时候还能对语言进行扩展,从系统层面解决项目中的一些设计或技术问题。了解 Runtime ,要先了解它的核心 - 消息传递 (Messaging)。

高级编程语言想要成为可执行文件需要先编译为汇编语言再汇编为机器语言,机器语言也是计算机能够识别的唯一语言,但是OC并不能直接编译为汇编语言,而是要先转写为纯C语言再进行编译和汇编的操作,从OC到C语言的过渡就是由runtime来实现的。然而我们使用OC进行面向对象开发,而C语言更多的是面向过程开发,这就需要将面向对象的类转变为面向过程的结构体。

上述都是官方的文档释义,有些晦涩无聊,接下来我们用代码来具体解释一下。

Runtime消息传递

一个对象的方法  [obj test],编译器转成消息发送objc_msgSend(obj, test),Runtime时执行的流程是这样的:

1.首先,通过objisa指针找到它的class;

2.在classmethod listtest;

3.如果class中没到test,继续往它的superclass中找 ;

4.一旦找到test这个函数,就去执行它的实现IMP

当然了,由于效率的问题,每个消息都遍历一次objc_method_list并不合理。所以需要把经常被调用的函数缓存下来,去提高函数查询的效率。这也就是objc_class中另一个重要成员objc_cache做的事情 - 再找到test之后,把test的method_name作为key,method_imp作为value给存起来。当再次收到test消息的时候,可以直接在cache里找到,避免去遍历objc_method_list。从前面的源代码可以看到objc_cache是存在objc_class结构体中的。

objec_msgSend的方法:

OBJC_EXPORTidobjc_msgSend(idself, SEL op, ...)

我们看看对象(object),类(class),方法(method)这几个的结构体:

类对象(objc_class)

Objective-C类是由Class类型来表示的,它实际上是一个指向objc_class结构体的指针

struct objc_class结构体定义了很多变量。结构体里保存了指向父类的指针、类的名字、版本、实例大小、实例变量列表、方法列表、缓存、遵守的协议列表等,由此可见,类对象就是一个结构体struct objc_class,这个结构体存放的数据就是元数据(metadata)。

实例(objc_object)


类对象中的元数据存储的都是如何创建一个实例的相关信息,就是从isa指针指向的结构体创建,类对象的isa指针指向的我们称之为元类(metaclass)

元类中保存了创建类对象以及类方法所需的所有信息,因此整个结构应该如下图所示:

实例对象、类对象与元类简图

struct objc_object结构体它的isa指针指向类对象;

类对象的isa指针指向了元类;

super_class指针指向了父类的类对象;

而元类的super_class指针指向了父类的元类;

有点绕口令的感觉,那么就可以用网上的一个神图来表示了:

图6 实例对象、类对象与元类的自闭环

通过上图我们可以看出整个体系构成了一个自闭环,如果是从NSObject中继承而来的上图中的Root class就是NSObject。


c1是通过一个实例对象获取的Class,实例对象可以获取到其类对象,类名作为消息的接受者时代表的是类对象,因此类对象获取Class得到的是其本身。

如果我们想要获取ISA指针的对象的话,可以用下面这两个函数

OBJC_EXPORTBOOLclass_isMetaClass(Classcls) OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0);

OBJC_EXPORTClassobject_getClass(idobj) OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0);

class_isMetaClass用于判断Class对象是否为元类,object_getClass用于获取对象的isa指针指向的对象。


通过代码可以看出,一个实例对象通过class方法获取的Class就是它的isa指针指向的类对象,而类对象不是元类,类对象的isa指针指向的对象是元类。

所以关于Runtime部分,我们总结一下就是:首先实例对象是一个结构体,这个结构体只有一个成员变量,指向构造它的那个类对象,这个类对象中存储了一切实例对象需要的信息包括实例变量、实例方法等,而类对象是通过元类创建的,元类中保存了类变量和类方法,这样就完美解释了整个类和实例是如何映射到结构体的。所以理解Runtime就是理解iOS在运行时他的数据存储以及他的类、实例、类对象、元类他们之间的关系和作用。

消息转发机制

 上述讲了很多Runtime的基本理解和概念,那到底他和消息转发有什么关系呢,以及怎么去运用它呢?这就要讲到iOS的消息转发机制了。

归根到底,Objective-C中所有的方法调用本质就是向对象发送消息。

1.类中创建方法 -(void)todoSomething;

2.iOS系统为这个方法创建一个编号即:SEL(todoSomething);并添加到方法列表中。(selector是SEL的一个实例,这点和IMP是不一样的,IMP是指向最终实现程序的内存地址的指针)

3.当调用这个方法的时候:[Object todoSomething]; 系统去方法列表中插手这个方法编号,查到就执行。

注意:我们在写C代码的时候,经常会用到函数重载,就是函数名相同,参数不同,但是这在Objective-C中是行不通的,因为selector只记了method的name,没有参数,所以没法区分不同的method。

所以如果调用了一个方法,就会进行一次发送消息会在相关的类对象中搜索方法列表,如果找不到则会沿着继承树向上一直搜索知道继承树根部(通常为NSObject),如果还是找不到并且消息转发都失败了就回执行doesNotRecognizeSelector:方法报unrecognized selector错。那么消息转发到底是什么呢?接下来将会逐一介绍最后的三次机会。

1.动态方法解析

Objective-C运行时会调用 +resolveInstanceMethod:或者 +resolveClassMethod:,让你有机会提供一个函数实现。如果你添加了函数并返回YES, 那运行时系统就会重新启动一次消息发送的过程。如下图实例

打印出了“Doing foo”

可以看到虽然没有实现foo:这个函数,但是我们通过class_addMethod动态添加fooMethod函数,并执行fooMethod这个函数的IMP。从打印结果看,成功实现了。

如果resolve方法返回 NO ,运行时就会移到下一步:forwardingTargetForSelector。

备用接收者

如果目标对象实现了-forwardingTargetForSelector:,Runtime 这时就会调用这个方法,给你把这个消息转发给其他对象的机会。

实现一个备用接收者的例子如下:

可以看到我们通过forwardingTargetForSelector把当前ViewController的方法转发给了Person去执行了。打印结果也证明我们成功实现了转发。

完整消息转发

如果在上一步还不能处理未知消息,则唯一能做的就是启用完整的消息转发机制了。

首先它会发送-methodSignatureForSelector:消息获得函数的参数和返回值类型。如果-methodSignatureForSelector:返回nil,Runtime则会发出-doesNotRecognizeSelector:消息,程序这时也就挂掉了。如果返回了一个函数签名,Runtime就会创建一个NSInvocation对象并发送-forwardInvocation:消息给目标对象。

也打印出“Doing foo”

这就是Runtime的三次转发流程。下面我们讲讲Runtime的实际应用


当系统自带的方法功能不够,可以给系统自带的方法扩展一些功能,并保持原有的功能。例如我想知道当前的URL是否为空如果每次都判断一下的话会很麻烦,如果我创建扩展来写,又不知道内部是如何实现的.

一、可以使用runtime交换方法。

二、也可以动态添加方法

三、 给分类添加属性


四、KVO实现

KVO的实现依赖于 Objective-C 强大的 Runtime,当观察某对象 A 时,KVO 机制动态创建一个对象A当前类的子类,并为这个新的子类重写了被观察属性 keyPath 的 setter 方法。setter 方法随后负责通知观察对象属性的改变状况。

五、消息转发(热更新)解决Bug(JSPatch)

关于消息转发,消息转发分为三级,我们可以在每级实现替换功能,实现消息转发,从而不会造成崩溃。JSPatch不仅能够实现消息转发,还可以实现方法添加、替换能一系列功能

六、实现NSCoding的自动归档和自动解档

原理描述:用runtime提供的函数遍历Model自身所有属性,并对属性进行encode和decode操作。

核心方法:在Model的基类中重写方法:


总结:在整个Objective-C运行中,所有的方法调用都是消息的发送或转发的过程,最后可以把的第一个图大致变成下面这样的,方便理解

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

推荐阅读更多精彩内容