iOS:运行时(Runtime) 消息传递

  1. 运行时概述
    Objective-C语言是一门动态语言,它将很多静态语言在编译和链接时期做的事放到了运行时来处理。这样子让我们在写代码时更具灵活性,如我们可以把消息转发给我们想要的对象,或者随意交换一个方法的实现等。

然而这种特性意味着Objective-C不仅需要一个编译器,还需要一个运行时系统来执行编译的代码。这个运行时系统即Objc Runtime。Objc Runtime其实是一个Runtime库,它基本上是用C和汇编写的,这个库使得C语言有了面向对象的能力。

Runtime库主要做下面几件事:

  1. 封装:在这个库中,对象可以用C语言中的结构体表示,而方法可以用C函数来实现,另外再加上了一些额外的特性。这些结构体和函数被runtime函数封装后,我们就可以在程序运行时创建,检查,修改类、对象和它们的方法了。

  2. 找出方法的最终执行代码:当程序执行[object doSomething]时,会向消息接收者(object)发送一条消息(doSomething),runtime会根据消息接收者是否能响应该消息而做出不同的反应。这就是消息传递,我们将在后面详细介绍。

  3. 简介
    1 应该很多初学 Objective-C 的人都会把概述中得那句[object doSomething]当成简单的方法调用吧,而忽视了“发送消息”这几个字的深刻含义。其实[object doSomething]应该用[receiver message]来表示,这样的表述更加精确,容易理解,[receiver message]会被编译器转化为:objc_msgSend(receiver, selector),如果消息有参数则转化为objc_msgSend(receiver, selector, arg1, arg2, ...);
    2 oc中很重要的“类”,它本质上是一个对象,在runtime中用结构体表示。

  4. 正文

  5. 一些概念及名词解释:

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

    Class isa  OBJC_ISA_AVAILABILITY;```
```#if !__OBJC2__
    Class super_class                       OBJC2_UNAVAILABLE;  // 父类
    const char *name                        OBJC2_UNAVAILABLE;  // 类名
    long version                            OBJC2_UNAVAILABLE;  // 类的版本信息,默认为0
    long info                               OBJC2_UNAVAILABLE;  // 类信息,供运行期使用的一些位标识
    long instance_size                      OBJC2_UNAVAILABLE;  // 该类的实例变量大小
    struct objc_ivar_list *ivars            OBJC2_UNAVAILABLE;  // 该类的成员变量链表
    struct objc_method_list **methodLists   OBJC2_UNAVAILABLE;  // 方法定义的链表
    struct objc_cache *cache                OBJC2_UNAVAILABLE;  // 方法缓存
    struct objc_protocol_list *protocols    OBJC2_UNAVAILABLE;  // 协议链表```
`#endif
} OBJC2_UNAVAILABLE;`

 在这个定义中,下面几个字段我们再解释一下:
1.isa:类的实例对象的 isa 指向它的类;类的 isa 指向该类的 meta-class。
2.super_class:指向该类的父类,如果该类已经是最顶层的根类(如NSObject或NSProxy),则super_class为nil
3.cache:用于缓存最近使用的方法。一个接收者对象接收到一个消息时,它会根据isa指针去查找能够响应这个消息的对象。在实际使用中,这个对象只有一部分方法是常用的,很多方法其实很少用或者根本用不上。这种情况下,如果每次消息来时,我们都是methodLists中遍历一遍,性能势必很差。这时,cache就派上用场了。在我们每次调用过一个方法后,这个方法就会被缓存到cache列表中,下次调用的时候runtime就会优先去cache中查找,如果cache没有,才去methodLists中查找方法。这样,对于那些经常用到的方法的调用,但提高了调用的效率。
4.ivars:ivars是objc_ivar_list指针 (objc_ivar_list 是类中得成员列表)
5.methodLists:methodLists是指向objc_method_list指针的指针。也就是说可以动态修改*methodLists的值来添加成员方法,这也是Category实现的原理,同样解释了Category不能添加属性的原因。(确实有需要的话可以在Category中添加@dynamic的属性,并利用运行期动态提供存取方法或干脆动态转发;或者干脆使用关联度对象(AssociatedObject))(objc_method_list是类中的方法列表)

 2.**消息传递**:我们之前在简介里面说的 消息发送其实质是这样的`id objc_msgSend ( id self, SEL op, ... )`当然还有不同方法,如下不同情况会调用不一样的方法,这里暂且不表。
![message文件下.png](http://upload-images.jianshu.io/upload_images/302190-fcfc4a1960ec342a.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
***先同样解释几个名词;***
 1.id:id大家应该对它都不陌生,它是一个指向类实例的指针:```typedef struct objc_object \* id;```而objc_object则是`struct objc_object { Class isa; };`
  2.SEL:objc_msgSend函数第二个参数类型为SEL,它是selector在Objc中的表示类型(Swift中是Selector类)。selector是方法选择器,可以理解为区分方法的 ID,而这个 ID 的数据结构是SEL: `typedef struct objc_selector \*SEL;`
其实它就是个映射到方法的C字符串,你可以用 Objc 编译器命令@selector()或者 Runtime 系统的sel_registerName函数来获得一个SEL类型的方法选择器。
不同类中相同名字的方法所对应的方法选择器是相同的,和c、c++不同的是,方法名字相同而变量类型不同的方法其实也是相同的方法选择器,于是 Objc 中方法命名有时会带上参数类型,所以Cocoa 中有好多很长的方法。
 
 ***objc_msgSend这个函数完成了动态绑定的所有事情:***
 1.首先它找到selector对应的方法实现。因为同一个方法可能在不同的类中有不同的实现,所以我们需要依赖于接收者的类来找到的确切的实现。
 2.它调用方法实现,并将接收者对象及方法的所有参数传给它。
 3.最后,它将实现返回的值作为它自己的返回值。

 消息的关键在于我们前面章节讨论过的结构体objc_class,这个结构体有两个字段是我们在分发消息的关注的:
 1.指向父类的指针
 2.一个类的方法分发表,即methodLists。

 这里我们再来看一下之前说的meta-class 
为什么会有meta-class这个东西呢,相信很多人已经发现objc_class中也有一个isa对象,这是因为一个 ObjC 类本身同时也是一个对象。而meta-class就是用来处理类与实例的关系。简单地理解就是对象是类的实例,而类是元类的实例。
再深入一下,meta-class也是一个类,也可以向它发送一个消息,那么它的isa又是指向什么呢?为了不让这种结构无限延伸下去,Objective-C的设计者让所有的meta-class的isa指向基类的meta-class,以此作为它们的所属类。即,任何NSObject继承体系下的meta-class都使用NSObject的meta-class作为自己的所属类,而基类的meta-class的isa指针是指向它自己。这样就形成了一个完美的闭环。

 通过上面的描述,再加上对objc_class结构体中super_class指针的分析,我们就可以描绘出类及相应meta-class类的一个继承体系了,如下图所示:
![�父类元类示意图](http://upload-images.jianshu.io/upload_images/302190-606d15a62ecd03f6.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

 从元类父类扯回来,所以当我们创建一个新对象时,会先为其分配内存,并初始化其成员变量。其中isa指针也会被初始化,让对象可以访问类及类的继承体系。

 下图演示了这样一个消息的基本框架:

![消息传递示意图](http://upload-images.jianshu.io/upload_images/302190-63d59bfdf17ec1fe.gif?imageMogr2/auto-orient/strip)
当消息发送给一个对象时,objc_msgSend通过对象的isa指针获取到类的结构体,然后在方法分发表里面查找方法的selector。如果没有找到selector,则通过objc_msgSend结构体中的指向父类的指针找到其父类,并在父类的分发表里面查找方法的selector。依此,会一直沿着类的继承体系到达NSObject类。一旦定位到selector,函数会就获取到了实现的入口点,并传入相应的参数来执行方法的具体实现。如果最后没有定位到selector,则会走消息转发流程,这个我们在后面讨论。
而在这个流程中,为了加快消息传递速度,这里就用上了我们之前说的cache。
3.**消息转发**:当一个对象无法接收某一消息时,就会启动所谓”消息转发(message forwarding)“机制,通过这一机制,我们可以告诉对象如何处理未知的消息。默认情况下,对象接收到未知的消息,会导致程序崩溃,相信很多人都会遇到unrecognized selector sent to instance这个崩溃吧。所以很多时候我们给它会加上一个判断respondsToSelector: 。其实,我们可以采取一些措施,让我们的程序执行特定的逻辑,而避免程序的崩溃。
消息转发机制基本上分为三个步骤:
 1.动态方法解析
 2.备用接收者
 3.完整转发
未完待续~~

你可能感兴趣的:(iOS:运行时(Runtime) 消息传递)