iOS RunTime之二:数据结构

由上面一章中,我们了解了什么是RunTimeRunTime用来做什么,下面了解一下Runtime数据结构。

我们知道在Objective-C中,使用[object doSomething]语法并不会马上执行object接受者对象的doSomething方法的代码,而是向object接受者对发送一条doSomething消息,这条消息可能由object接受者对来处理,也可能由转发给其他对象来处理,也有可能假装没有接收到这条消息而没有处理。

其实[object doSomething]被编译器转化为:

id objc_msgSend ( id self, SEL op, ... );

下面从两个数据结构idSEL来逐步分析和理解Runtime有哪些重要的数据结构。

id

objc_msgSend方法里面的第一个参数的数据类型id,通用类型指针,能够表示任何对象。

Paste_Image.png

查看源文件,可以看出id其实就是一个指向objc_object结构体指针,它包含一个Class isa成员,根据Class isa指针就可以找到对象所属的类。

Class

从源文件看出,Objective-C的对象就是一个包含isa指针的数据结构,而isa指针的数据类型是ClassClass表示对象所属的类。

Paste_Image.png

从源文件看出,Class其实就是一个objc_class结构体指针。objc_class结构体定义如下:

isa:在Objective-C中,所有的类自身也是一个对象,即类对象。在这个类对象里面也有一个isa指针,它指向metaClass(元类)。
super_class:指向该类的父类,如果该类已经是最顶层的根类(如NSObject),则super_class为NULL。
name:这个类的类名。
version:提供类的版本信息,这对于对象的序列化非常有用,它可是让我们识别出不同类定义版本中实例变量布局的改变。
info:类信息,供运行期使用的一些位标识。
instance_size:该类的实例变量大小。
ivars:该类的成员变量链表。
methodLists:方法定义的链表。
protocols:协议链表。
cache:一个接收者对象接收到一个消息时,它会根据isa指针去查找能够响应这个消息的对象。在实际使用中,这个对象只有一部分方法是常用的,很多方法其实很少用或者根本用不上。这种情况下,如果每次消息来时,我们都是methodLists中遍历一遍,性能势必很差。这时,cache就派上用场了。在我们每次调用过一个方法后,这个方法就会被缓存到cache列表中,下次调用的时候runtime就会优先去cache中查找,如果cache没有,才去methodLists中查找方法。这样,对于那些经常用到的方法的调用,但提高了调用的效率。

注意:

  • 在面向对象设计中,一切都是对象,Class在设计中本身也是一个对象。
  • 由此可见,结构体objc_class也是继承objc_object,说明Class在设计中本身也是一个对象。

元类(Meta Class)

Objective-C中,所有的类自身也是一个对象,这个对象里面也有一个isa指针,它指向metaClass(元类),向这个对象发送消息(即调用类方法)。

Paste_Image.png

从图中看出:

  • 当我们向一个对象发送消息时,isa指针会在这个对象所属的这个类的方法列表中查找方法;
  • 向一个类发送消息时,isa指针会在这个类的meta-class的方法列表中查找。meta-class之所以重要,是因为它存储着一个类的所有类方法。
  • 每个类都会有一个单独的meta-class,因为每个类的类方法基本不可能完全相同。

Objective-C的设计者让所有的meta-class的isa指向基类的meta-class,以此作为它们的所属类。即,任何NSObject继承体系下的meta-class都使用NSObject的meta-class作为自己的所属类,而基类的meta-class的isa指针是指向它自己,这样就形成了一个完美的闭环。

SEL

objc_msgSend函数第二个参数类型为SEL,它是selectorObjc中的表示类型(Swift中是Selector类)。selector是方法选择器,可以理解为区分方法的id,而这个id的数据结构是SEL,即表示一个方法的selector的指针。

Paste_Image.png

  • 方法的selector用于表示运行时方法的名字,Objective-C在编译时,会依据每一个方法的名字、参数序列,生成一个唯一的整型标识(int类型的地址),这个标识就是SEL
  • Objective-C中,只要方法名相同,那么方法的SEL就是一样的,每一个方法都对应着一个SEL,所以在Objective-C中,同一个类中或者这个类的继承体系中,不能存在2个同名的方法,不同的类可以拥有相同的selector,不同的类的实例对象执行相同的selector,会在各自的方法列表中根据selector去寻找对应的IMP
  • 在本质上,SEL只是一个指向方法的指针(被hash化得KEY值),能提高方法的查询速度。

IMP

IMP就是implementation的缩写,本质就是一个函数指针,这个被指向的函数包含一个接收消息的对象id,调用方法的SEL,以及一些方法参数,并返回一个id。因此我们可以通过SEL获得它所对应的IMP,在取得了函数指针之后,也就意味着我们取得了需要执行方法的代码入口,这样我们就可以像普通的C语言函数调用一样使用这个函数指针。

Paste_Image.png

SEL就是为了查找方法的最终实现IMP的,由于每个方法对应唯一的SEL,因此我们可以通过SEL方便快速准确地获得它所对应的IMP

Method

Method是一种代表类中的某个方法的类型。

Paste_Image.png

objc_method在上面的方法列表中提到过,它存储了方法名,方法类型和方法实现:

Paste_Image.png

注意:

  • 方法名类型为SEL,前面提到过相同名字的方法即使在不同类中定义,它们的方法选择器也相同。
  • 方法类型method_types是个char指针,其实存储着方法的参数类型和返回值类型。
  • method_imp指向了方法的实现,本质上是一个函数指针。

Ivar

Ivar是一种代表类中实例变量的类型。

Paste_Image.png

Paste_Image.png

Cache

Paste_Image.png

Cache其实就是一个存储Method的链表,主要是为了优化方法调用的性能。

当对象receiver调用方法message时,首先根据对象receiverisa指针查找到它对应的类,然后在类的methodLists中搜索方法,如果没有找到,就使用super_class指针到父类中的methodLists查找,一旦找到就调用方法。如果没有找到,有可能消息转发,也可能忽略它。但这样查找方式效率太低,因为往往一个类大概只有20%的方法经常被调用,占总调用次数的80%。所以使用Cache来缓存经常调用的方法,当调用方法时,优先在Cache查找,如果没有找到,再到methodLists查找。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

扫码关注云+社区

领取腾讯云代金券