介绍
重要: 该文档已经不再更新. 需要了解 Apple SDKs 的最新信息, 请访问 . |
---|
Objective-C 语言尽可能地从编译时和链接时到 runtime (运行时)推迟决定。只要可能的话,它都会动态地完成任务。这意味着这个语言不仅需要一个编译器(compiler),而且需要一个 runtime 系统(runtime system)来执行编译过的代码。Runtime 系统对于 Objective-C 语言来说表现地像是一种操作系统;这也是这个语言运作的原因(it’s what makes the language work)。
该文档介绍了NSObject
类以及 Objective-C 程序如何与 runtime 系统交互。 特别是,它检查了在 runtime 动态加载新的类和转发消息给其他对象的范例(paradigms)。它还提供了一些信息,这些信息是关于当你的程序正在跑的时候你如何找到对象的信息。
你应该阅读该文档以增加一些关于 Objective-C runtime 系统如何工作以及你如何使用它的理解。但是在通常情况下,你应该没有理由需要了解和理解这个材料来写 Cocoa 应用。
该文档的组织架构
该文档有如下章节:
-
Runtime 的版本和平台
-
与 Runtime 交互
-
消息
-
动态方法解析
-
消息转发
-
类型编码
-
声明的属性
更多资料
Runtime 的版本和平台
在不同的平台上有不同的 Objective-C runtime 版本。
Legacy(遗产)版本和 Modern(现代) 版本
Modern runtime 最重要的新特性是实例变量是“非易碎的(non-fragile)”:
-
在 legacy runtime 中,如果你改变一个类中实例变量的布局(layout),那么你必须重新编译继承自它的类。
-
在 modern runtime 中,如果你改变一个类中实例变量的布局(layout),你不需要重新编译继承自它的类。
平台
iPhone 应用和OS X v10.5 或更近的版本的64位程序使用 modern 版本的 runtime。
其他的程序(OS X 桌面系统的32位程序)使用 legacy 版本的 runtime。
和 Runtime 交互
Objective-C 程序在三个不同的层级上和 runtime 系统进行交互:通过 Objective-C 源码;使用 Foundation 框架中的 NSObject
类定义的方法;通过直接调用 runtime 函数。
Objective-C 源码
在大多数情况下,runtime 系统在背后自动工作。你只需要编写和编译 Objective-C 源码就可以使用它。
NSObject 方法
例如,NSObject
类定义了一个 description
实例方法,这个方法返回一个用于描述该类的内容的字符串。它主要用于调试— GDB 的 print-object
命令输出这个方法返回的字符串。NSObject
对这个方法的实现不知道这个类包含什么,所以它返回一个带有对象名称和地址的字符串。NSObject
的子类会实现这个方法以返回更多细节。例如,Foundation 类 NSArray
返回一个它所包含的所有对象的列表。
NSObject
的一些方法简单地查询 runtime 系统的信息。这些方法允许对象执行内省(perform introspection)。这类方法的一些例子如 类
方法(class
method),要求对象识别它所属的类;isKindOfClass:
和 isMemberOfClass:
,检测对象在继承层级的位置;respondsToSelector:
,表示对象是否可以接受特定的消息;conformsToProtocol:
,表示对象是否声称实现特定协议中定义的方法;methodForSelector:
,提供方法的实现的地址。这类方法使对象能够自省内省(introspect about itself)。
Runtime 函数
消息发送(Messaging)
The objc_msgSend 函数
In Objective-C, messages aren’t bound to method implementations until runtime. The compiler converts a message expression,
在 Objective-C 中,消息知道 runtime 才会被绑定到方法实现。编译器会将消息表达式,
[receiver message]
objc_msgSend(receiver, selector)
任何在消息中传递的其他参数也会传递给 objc_msgSend
:
objc_msgSend(receiver, selector, arg1, arg2, ...)
消息发送函数可以完成动态绑定所需的一切:
-
它首先找到选择器引用的程序(方法的实现)。由于不同的类可以实现相同的方法,因此它根据接收者(receiver)的类来找到准确的程序。
-
然后它调用程序,将接收对象(指向对象数据的指针)传递给它,同时传递这个方法指定的所有参数。
-
最后,它将程序的返回值作为它自己的返回值传递。
注意:编译器生成消息发送函数的调用。你永远不要在你写的代码中去调用它 |
---|
消息发送最重要的点在于编译器为每个类和对象构建的结构体。每个类结构体都包含以下两个要素:
-
一个指向父类的指针。
-
类派遣表(dispatch table)。这个表具有将方法选择器和它们标识的方法的特定于类的地址相关联的条目(读不懂,原文:This table has entries that associate method selectors with the class-specific addresses of the methods they identify)。
setOrigin::
方法的选择器与setOrigin::
的地址(实现的程序)相关联,display
方法的选择器与display
的地址相关联,等等。
创建新对象时,将为它分配内存,并初始化其实例变量。其中,对象的第一个变量是指向它的类结构体的指针。这个指针,又称为 isa
,使对象可以访问它的类,并通过该类访问继承自的所有类。
注意:虽然不是严格意义上的语言的一部分,但是对象需要 isa 指针才能使用 Objective-C runtime 系统。在结构定义的任何字段中,对象需要与struct objc_object (在objc / objc.h 中定义)“等效(equivalent)”。但是,你很少(如果有的话)需要创建自己的根对象(root object),并且继承自 NSObject 或 NSProxy 的对象会自动具有 isa 变量。 |
---|
类和对象结构体的这些要素如下图所示
当消息被发送给对象时,消息发送函数跟随对象的
isa
指针找到类结构体,并在结构体的派遣表中查找方法选择器。如果不能找到选择器,objc_msgSend
就跟随指针找到父类,然后尝试在派遣表中找到选择器。连续的查找失败会使 objc_msgSend
沿着类的继承层级向上直到 NSObject
类。一旦定位到选择器,消息发送函数就会调用表中的方法,并且传入对象的数据结构(参数?)。
这就是在 runtime 下方法的实现被选择的方式—或者用面向对象编程的术语来说,方法被动态地绑定到消息。
TO BE CONTINUED...