runtime一直给我的感觉是很难很高深的一个知识点,闲来无事,学习一波
在看这篇文章之前,你可以先看看这张图片,对runtime有个大概的了解
开篇:众所周知oc是一门基于C的动态语言,举个例子,当你调用一个方法而没有去实现时,程序编译能正常通过,而对于C语言这种静态语言编译的时候就实现了方法的调用,所以在C中如果你使用了一个没有实现的方法的话编译阶段就会报错。OC中当程序执行[object doSomething]时,会向消息接收者(object)发送一条消息(doSomething),runtime会根据消息接收者是否能响应该消息而做出不同的反应。
runtime的几个术语
1.class( objc_class)
Objective-C类是由Class类型来表示的,它实际上是一个指向objc_class结构体的指针。
可以看到运行时一个类还关联了它的超类指针,类名,成员变量,方法,缓存,还有附属的协议.其中objc_ivar_list和objc_method_list分别是成员变量列表和方法列表
如果你C语言不是特别好,可以直接理解为objc_ivar_list结构体存储着objc_ivar数组列表,而objc_ivar结构体存储了类的单个成员变量的信息;同理objc_method_list结构体存储着objc_method数组列表,而objc_method结构体存储了类的某个方法的信息
2.id (objc_object)
id(对象)是一个指向类实例objc_object的指针
可以看到,这个结构体只有一个字体,即指向其类的isa指针。这样,当我们向一个Objective-C对象发送消息时,运行时库会根据 实例对象的isa指针找到这个实例对象所属的类。Runtime库会在类的方法列表及父类的方法列表中去寻找与消息对应的selector指向的方法,找到后即运行这个方法
3.元类(Meta Class)
meta-class是一个类对象的类。
在上面我们提到,所有的类自身也是一个对象,我们可以向这个对象发送消息(即调用类方法)。既然是对象,那么它也是一个objc_object指针,它包含一个指向其类的一个isa指针。那么,这个isa指针指向什么呢?为了调用类方法,这个类的isa指针必须指向一个包含这些类方法的一个objc_class结构体。这就引出了meta-class的概念,meta-class中存储着一个类的所有类方法。所以,调用类方法的这个类对象的isa指针指向的就是meta-class当我们向一个对象发送消息时,runtime会在这个对象所属的这个类的方法列表中查找方法;而向一个类发送消息时,会在这个类的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类的一个继承体系了,如下代码
4.SEL(方法选择器)
SEL又叫选择器,是表示一个方法的selector的指针,其定义如下:
方法的selector用于表示运行时方法的名字。Objective-C在编译时,会依据每一个方法的名字、参数序列,生成一个唯一的整型标识(Int类型的地址),这个标识就是SEL。两个类之间,只要方法名相同,那么方法的SEL就是一样的,每一个方法都对应着一个SEL。所以在Objective-C同一个类(及类的继承体系)中,不能存在2个同名的方法,即使参数类型不同也不行当然,不同的类可以拥有相同的selector,这个没有问题。不同类的实例对象执行相同的selector时,会在各自的方法列表中去根据selector去寻找自己对应的IMP。工程中的所有的SEL组成一个Set集合,如果我们想到这个方法集合中查找某个方法时,只需要去找到这个方法对应的SEL就行了,SEL实际上就是根据方法名hash化了的一个字符串,而对于字符串的比较仅仅需要比较他们的地址就可以了,可以说速度上无语伦比!
本质上,SEL只是一个指向方法的指针(准确的说,只是一个根据方法名hash化了的KEY值,能唯一代表一个方法),它的存在只是为了加快方法的查询速度。通过下面三种方法可以获取SEL:
a、sel_registerName函数
b、Objective-C编译器提供的@selector()
c、NSSelectorFromString()方法
5.IMP(方法的实现地址入口,莫名想到某adc....)
IMP实际上是一个函数指针,指向方法实现的地址。
其定义如下:
第一个参数:是指向self的指针(如果是实例方法,则是类实例的内存地址;如果是类方法,则是指向元类的指针)
第二个参数:是方法选择器(selector)
接下来的参数:方法的参数列表。
前面介绍过的SEL就是为了查找方法的最终实现IMP的。由于每个方法对应唯一的SEL,因此我们可以通过SEL方便快速准确地获得它所对应的IMP,查找过程将在下面讨论。取得IMP后,我们就获得了执行这个方法代码的入口点,此时,我们就可以像调用普通的C语言函数一样来使用这个函数指针了。通过一组id和SEL参数就能确定唯一的方法实现地址;反之亦然。
6.Method
Method是一种代表类中的某个方法的类型。其实是指向objc_method结构体的指针
而objc_method在上面的方法列表中提到过,它存储了方法名,方法类型和方法实现:
方法名类型为SEL,前面提到过相同名字的方法即使在不同类中定义,它们的方法选择器也相同。
方法类型method_types是个char指针,其实存储着方法的参数类型和返回值类型。
method_imp指向了方法的实现,本质上是一个函数指针,前面已经讲到。
我们可以看到该结构体中包含一个SEL和IMP,实际上相当于在SEL和IMP之间作了一个映射。有了SEL,我们便可以找到对应的IMP,从而调用方法的实现代码。
7.Ivar
Ivar是一种代表类中实例变量的类型
而objc_ivar在上面的成员变量列表中也提到过:
8.Cache
在runtime.h中Cache的定义如下:
还记得之前objc_class结构体中有一个struct objc_cache *cache吧,它到底是缓存啥的呢,先看看objc_cache的实现:
Cache为方法调用的性能进行优化,通俗地讲,每当实例对象接收到一个消息时,它不会直接在isa指向的类的方法列表中遍历查找能够响应消息的方法,因为这样效率太低了,而是优先在Cache中查找。Runtime 系统会把被调用的方法存到Cache中(理论上讲一个方法如果被调用,那么它有可能今后还会被调用),下次查找的时候效率更高。这根计算机组成原理中学过的 CPU 绕过主存先访问Cache的道理挺像,而我猜苹果为提高Cache命中率应该也做了努力吧
剩下的内容可以参考我给出的两个链接里面后来的内容包括(动态方法解析,消息的转发机制以及Method Swizzling等),写的很好,我这篇文章也只是将这两篇文章中的写的好的内容整理了出来
花了一个下午和一个上午看完这几篇文章后,神清气爽
参考链接:
这几个链接是一起的:
(Objective-C Runtime - 文章 - 伯乐在线,
Objective-C Runtime 运行时之四:Method Swizzling - 文章 - 伯乐在线
Objective-C Runtime 运行时之五:协议与分类 - 文章 - 伯乐在线
Objective-C Runtime 运行时之六:拾遗 - 文章 - 伯乐在线)
简书文章: iOS运行时(Runtime)详解+Demo - 简书
其他学习资料:
iOS黑魔法-Method Swizzling - CocoaChina_让移动开发更简单
使用RunTime实现字典转模型 - 简书(可以看看MJExtension的源码)