ObjCRuntimeGuide小记

Runtime System对于Objective-C来说就好比是它的操作系统/运行平台,它使得Objective-C代码能跑得起来。

相对于C/C++来说,Objective-C尽可能地把一些动作推迟到运行时来执行,即尽可能动态地做事情。因此,它不仅需要一个编译器,还需要一个运行时环境来执行编译后的代码。

这里会讨论到NSObject类,Objective-C程序如何与Runtime System交互,运行时动态地加载新类,发消息给其它对象,以及运行时如何获取对象信息。

Runtime System分为Legacy和Modern两个版本,一般来说,我们现在用的都是Modern版本。

Modern版本的Runtime System有一个显著的特征就是“non-fragile”,即父类的成员变量的布局发生改变时,子类不需要重新编译。此外,还支持为声明的属性进行合成操作(即@property和@synthesis)。

与Runtime System交互

Objective-C程序和Runtime System在三个不同层次进行交互:通过Objective-C源码;通过NSObject定义的函数;以及通过直接调用runtime functions。

通常来讲,Runtime System都是在幕后工作,我们需要做的就是编写Objective-C代码,然后编译。编译器会为我们创建相应的数据结构和函数调用来实现语言的动态特性。这些数据结构保存着类、Category定义和Protocol声明中所能找到的信息,包括成员变量模板、selectors,以及其它从源码中提取到的信息。最主要的Runtime函数是用来发送消息的,它由源码中的消息表达式激发。

Cocoa中大部分对象都是NSObject的子类(NSProxy是一个例外),继承了NSObject的方法。因此在这个继承体系中,子类可以根据需求重新实现NSObject定义的一些函数,实现多态和动态性,比如description方法。

一些NSObject定义的方法只是简单地询问Runtime System获得信息,使得对象可以进行自省(introspection),比如一些类方法:用来确定类类型的isKindOfClass:,确定对象在继承体系中的位置的isMemberOfClass:,判断一个对象是否能接收某个特定消息的respondsToSelector:,判断一个对象是否遵循某个协议的conformsToProtocol:,以及提供方法实现地址的methodForSelector:。这些方法让一个对象可以进行自省(introspect about itself)。

Runtime System是一个动态共享库,位于/usr/include/objc,拥有一套公共的接口,由一系列函数和数据结构组成。开发人员可以使用纯C调用一些函数来做编译器做的事情,或者扩展Runtime System,为开发环境制作一些工具等等。尽管一般情况下,编写Objective-C并不需要了解这些内容,但有时候会很有用。所有的函数都在Objective-C Runtime Reference有文档化信息。

发送消息是Objective-C程序中最经常出现的表达式,而该表达式最终会被转换成objc_msgSend函数调用。

比如一个消息表达式[receiver message]会被转换成objc_msgSend(receiver, selector),如果有参数则为objc_msgSend(receiver, selector, arg1, arg2, …)。

消息只有到运行时才会和函数实现绑定起来:首先objc_msgSend在receiver中查找selector对应的函数实现;然后调用函数过程,将receiving object(即this指针)和参数传递过去;最后,返回函数的返回值。

发送消息的关键是编译器为类和对象创建的结构,包含两个主要元素,一个是指向superclass的指针,另一个是类的dispatch table,该dispatch table中的表项将selector和对应的函数入口地址关联起来。

当一个对象被创建时,内存布局中的第一个元素是指向类结构的指针,isa。通过isa指针,一个对象可以访问它的类结构,进而访问继承的类结构。示例图可参见:ObjCRuntimeGuide第14页。

当向一个对象发送消息时,objc_msgSend先通过isa指针在类的dispatch table中查找对应selector的函数入口地址,如果没有找到,则沿着class hierarchy(继承体系)寻找,直到NSObject类。这就是在运行时选择函数实现,用OOP的行话来说,就是动态绑定。

为了加速发送消息的速度,Runtime System为每个类创建了一个cache,用来缓存selector和对应函数入口地址的映射。

当objc_msgSend找到对应的函数实现时,它除了传递函数参数,还传递了两个隐藏参数:receiving object和selector。之所以称之为隐藏参数,是因为这两个参数在源代码中没有显示声明,但还是可以通过self和_cmd来访问。

当一个消息要被发送给某个对象很多次的时候,可以直接使用methodForSelector:来进行优化,比如下述代码:

//////////////////////////////////////////////////////////////    void (*setter)(id, SEL, BOOL);   int i;      setter = (void (*)(id, SEL, BOOL))[target        methodForSelector:@selector(setFilled:)];   for ( i = 0; i < 1000, i++ )         setter(targetList[i], @selector(setFilled:), YES);   //////////////////////////////////////////////////////////////  

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/wyzygj.html