最近在看某些书籍和博客的时候不时地有Runtime的出现,查了相关介绍之后觉得还是没有彻底地理解其机理,于是今天把官方文档给煲了一下,下面结合相关介绍,做一个相对全面的学习笔记。
Abstract
Runtime是Object-C特有的一种动态运行机制,compiler中的runtime system实现这种机制。
其主要在三方面实现:
第一是我们写的原代码,protocal、instance varible;
第二是Foundation Framework中的方法,如NSObject,NSProxy;
第三是直接使用plain C纯C语言的方法来实现。
接下来我们来看看这三方面分别是怎样一种实现机理。
第一
主要是指消息机制,最典型的就是我们所写代码中的函数调用,便是通过runtime system来实现的。
在介绍其实现过程之前我们先来剖析objc中的类变量是如何在内存中组织的。
在我们创建一个类的时候,系统会为这个类分配一段内存空间,用来存放其属性变量。而在这些属性变量的最开始,也就是内存的第一个空间,存放着每个类所特有的isa指针,如图fig-1.1。
*isa |
varible 1 |
varible 2 |
... |
这个指针指向的是这个类的信息结构体class structures,结构体由两个变量组成,一个指向父类structures的指针还有一个指向类方法地址表格methor address table的指针,如图fig-1.2,图fig-1.3。由最小子类向上攀升,isa一层层向上指,直至NSObject基类。
class structures |
* superclass pointer |
* methor address list |
methor list |
address 1 |
... |
有了类在内存中组织的结构的概念之后,我们再来分析我们所写代码的消息机制如何在runtime 中实现。
当我们在代码中调用某一函数,比如:
[object methor];
那么经runtime system时将会调用
objc_msgSend(receiver,selector);
来告知receiver调用selector函数。
再receiver类收到消息之后,其会启动一下过程寻找函数的实现IMP地址。
首先在私有内存空间中通过isa找到class structures,随后访问methor table,看是否有对应函数地址存在,如有便返回,如没有则通过super class structures pointer继续向上层的methor table寻找,直至寻找到对应的IMP地址或到NSObject都没找到则返回错误信息。假如找到对应的函数地址则调用并返回返回值,如图 fig-1.4。
为了防止每次寻找地址空间耗费过多的时间,runtime system准备了一个cache table来存放已经找到的函数地址空间,当一个函数调用多次时便可以节省寻找时间,所以receiver 接收到消息之后应该是先在cache table中寻找,找不到时在执行后续的寻找,如下图fig-1.5。
以上便是objc中通过rumtime实现函数的调用过程。
值得一提的是在objc_msgSend函数中的receiver和selector函数实际上的隐藏参数,因为在objc层面其并没有直接显示出来。
不过这两个参数还是有作用的,特别是self参数,如下(引用自官方文档):
- strange
{
id target = getTheReceiver();
SEL method = getTheMethod();
if ( target == self || method == _cmd )
return nil;
return [target performSelector:method];
}
对于以上函数,我不是很理解,大家多多指教。
第二
runtime interact with methor in Foundation Framework这方面主要指的是NSObject提供的通过runtime system实现的函数,如description方法,其返回一段对于类的描述,最典型是用于debugge,比如数字类返回的是对于成员的描述。其他的诸如
isKindOfClass:
isMemberOfClass:
respondsToSelector:
conformsToProtocol:
methodForSelector:
等通过runtime实现的包含在NSObject的函数,值得注意的是这些函数并不是objc函数,而是由runtime system提供的函数。
当我们在一个loop循环中持续调用某个函数时,这会频繁的通过runtime去寻找函数实现地址,如第一点中所提到的过程,此时circumvent dynamic binding,绕过runtime动态绑定而调用函数的方法便是使用methorforSelector:直接找到函数的地址,这样在多次的循环中,可以节省很多时间。
其次重要的方面便是通过runtime system进行方法的动态implementation实现。
对属性声明
@dynamic propertyName;
告诉编译器这个属性有关的方法是动态生成的,进而编写函数实现代码
void dynamicMethodIMP(id self, SEL _cmd) {
// implementation ....
}
以及动态生成过程
@implementation MyClass
+ (BOOL)resolveInstanceMethod:(SEL)aSEL
{
if (aSEL == @selector(resolveThisMethodDynamically)) {
class_addMethod([self class], aSEL, (IMP) dynamicMethodIMP, "v@:");
return YES;
}
return [super resolveInstanceMethod:aSEL];
}
@end
当检测到SEL是
resolveThisMethodDynamically
即动态实现函数时,便调用class_addMethod来动态实现。比较常见的便是coredata中关联类的属性声明。
动态加载机制为objc解决了很多事情,实现了很多功能,当类被创建时,协议被创建时我们都可以在运行时动态地添加相关功能module。Cocoa框架中有很多也是通过动态加载来实现的。
第二点最后一个内容是forwarding Message的由runtime system提供消息转发机制,关于剩下的部分在下一篇中会提到。未完待续。。