探究Objective-C - 理解Runtime

本文为我个人对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的字样。

你可能感兴趣的:(探究Objective-C - 理解Runtime)