本文为我个人对Runtime的理解,如果你有不同见解,欢迎在下方评论区写下你的想法。
Runtime本质上是一个用C语言和汇编语言编写的库,但它不是一个普普通通的系统库,它从C语言旁走过,带着C语言的意志,凭借自身优势为Objective-C打造出了面向对象的功能,然后与iOS程序一起共赴了一场面向对象编程的舞会。
Runtime是如何支撑起Objective-C面向对象的能力的呢?下面本文将从两个主要的方面来对此问题进行分析:
不只是对象有Class,Class也有Class
一门语言要想做到面向对象,那么它就需要有既能封装数据又能封装方法的数据结构,退一步讲,至少是能对方法调用做出响应的数据结构,Runtime就是通过做到后面一条,从而为Objective-C中提供了面向对象的能力。
从实现的角度来讲,Objective-C中的对象在底层仍然是以structure的形式存储的,但是与通常的structure数据不同的是,对象需要有一个指向它自己Class类型的isa指针:
typedef struct objc_class *Class;
typedef struct objc_object {
Class isa;
} *id;
Class可以理解为对实例的描述,包括实例需要包含哪些属性、支持哪些方法以及super class是谁等等。在Objective-C中,Class里存放着对象的方法列表,当在某个对象上进行方法调用时,Runtime会根据对象的isa指针找到它的Class,然后再在Class里的方法列表中查找是否有相应的方法实现可以调用,如果没有则再去对象的SuperClass中查找。
上面提到的这部分关于实例的内容其实很好理解,但是接下来我们要看一个Objective-C中特有的设计方式,这个方式有点烧脑,可能不太好理解,那就是Class也是一个有着isa指针的structure,从数据结构的角度来考虑,Class其实也是对象:
struct objc_class {
Class isa;
Class super_class;
/* followed by runtime specific details... */
};
为什么要这么做呢?Objective-C中除了有实例方法,还有类方法,所以Class也需要有一个对它做了描述或者说下了定义的数据结构,在这个数据结构中要存有Class的方法列表,在Objective-C中,这个数据结构叫做meta Class。每个Class都有一个meta Class作为它的类,这个meta Class就是Class中isa指向的地方。
就下来如果继续探究,我们会发现meta Class其实也是对象,它的Class是它所定义的Class的root Class的meta Class,这个就更绕了,不过一般情况下我们不需要考虑meta Class的响应链,所以这个不需要深究。
关于底层数据结构的逻辑实际应该是较为复杂的,但是简单来说就是:通过isa指针的串联,Objective-C为平平无奇的structure加上了method,从而支持了对象的概念。
isa指针:小小的身体,大大的能量
isa指针虽然就只做了为对象引用它的Class这一件事,但这件事的影响却非同小可,它使得Objective-C支持动态加载类型,使得Objective-C成为了一门动态变成语言。具体来讲就是:在编译阶段,编译器并不知道Objective-C代码中对象的类型,只有到了运行阶段,操作某一个对象时,系统才会根据isa指针去寻找它的类型。
Objective-C的动态性使得动态替换方法成为了可能,动态替换不仅可以替换方法实现,甚至可以替换被操作的对象。
在对数据结构有了一些了解后,我们来看一下调用对象的方法时会发生什么,从而了解下Runtime构造的方法响应链是什么样子的。
方法调用
假设在一个对象上调用了一个方法,代码如下方所示:
id returnValue = [someObject messageName:parameter];
这里使用了messageName来指代方法,而没有使用methordName,原因是OC会把方法名与它的参数当作一个消息来对待。
编译时,这行代码将被替换为:
id returnValue = objc_msgSend(someObject, @selector(messageName:),
parameter);
在运行时,Runtime会根据对象的isa指针去找到它的类,进而在类的方法列表中查找是否有该方法,如果没有找到,则根据类中的super指针向父类中去查找,如果仍未找到,则在继承体系中继续向上查找,直至root类为止。如果在这个查找的过程中Runtime找到了该方法的实现,那么它就会很愉快地去执行这个方法了,而一旦没有找到任何实现,那么消息转发就会开始了。
消息转发
当Runtime在被调用的对象上以及它所处的继承体系中都找不到方法的实现时,消息转发将被触发。
首先,会调用
+ (BOOL)resolveInstanceMethod:(SEL)selector
在这个方法中,我们可以根据情况为对象添加方法:
class_addMethod(self, selector, (IMP)someIMP, "v@:@")
+(BOOL)resolveInstanceMethod:(SEL)selector的返回值为布尔类型,返回YES则表示调用成功,消息转发也随即结束,返回NO则表示失败,将继续执行消息转发中剩余的流程。
如果在上一步流程中返回了NO,那么将调用
- (id)forwardingTargetForSelector:(SEL)selector
在这个方法中,我们可以更改被调用的对象,它的返回值为对象类型,返回非空对象则表示调用成功,消息转发也随即结束,返回空指针则表示失败,将继续执行消息转发中剩余的流程。
如果在上一步流程中返回了空指针,那么将调用
- (void)forwardInvocation:(NSInvocation*)invocation
在这个方法中,我们可以对这次调用做修改。
NSObject中对上述流程中的方法都提供了实现:
- +(BOOL)resolveInstanceMethod:(SEL)selector"中返回了NO;
- -(id)forwardingTargetForSelector:(SEL)selector中返回了空指针;
- 而在(void)forwardInvocation:(NSInvocation*)invocation中打印我们经常会见到的那段关于unrecognized selector sent to instance的描述。
这也就是为什么当在一个类上调用一个它不支持的方法时,我们会经常在控制台看到unrecognized selector sent to instance的字样。