Runtime的特性主要是消息(方法
)传递,如果消息(方法
)在对象中找不到,就进行转发,具体怎么实现的呢。我们从下面几个方面探寻Runtime的实现机制。
-
Runtime介绍
-
Runtime消息传递
-
Runtime消息转发
-
Runtime应用
1、Runtime介绍
Objective-C 扩展了 C 语言,并加入了面向对象特性和 Smalltalk 式的消息传递机制。而这个扩展的核心是一个用 C 和 编译语言 写的 Runtime 库。它是 Objective-C 面向对象和动态机制的基石。
Objective-C 是一个动态语言,这意味着它不仅需要一个编译器,也需要一个运行时系统来动态得创建类和对象、进行消息传递和转发。理解 Objective-C 的 Runtime 机制可以帮我们更好的了解这个语言,适当的时候还能对语言进行扩展,从系统层面解决项目中的一些设计或技术问题。了解 Runtime ,要先了解它的核心 - 消息传递 (Messaging)。
Runtime
其实有两个版本: “modern
” 和 “legacy
”。我们现在用的 Objective-C 2.0
采用的是现行 (Modern
) 版的 Runtime
系统,只能运行在 iOS
和 macOS 10.5
之后的 64
位程序中。而 macOS
较老的32
位程序仍采用 Objective-C 1
中的(早期)Legacy
版本的 Runtime
系统。这两个版本最大的区别在于当你更改一个类的实例变量的布局时,在早期版本中你需要重新编译它的子类,而现行版就不需要。
Runtime
基本是用 C
和汇编
写的,可见苹果为了动态系统的高效而作出的努力。你可以在这里下到苹果维护的开源代码。苹果和GNU各自维护一个开源的 runtime 版本,这两个版本之间都在努力的保持一致。
平时的业务中主要是使用官方Api,解决我们框架性的需求。
高级编程语言想要成为可执行文件需要先编译为汇编语言再汇编为机器语言,机器语言也是计算机能够识别的唯一语言,但是OC
并不能直接编译为汇编语言,而是要先转写为纯C
语言再进行编译和汇编的操作,从OC
到C
语言的过渡就是由runtime来实现的。然而我们使用OC
进行面向对象开发,而C
语言更多的是面向过程开发,这就需要将面向对象的类转变为面向过程的结构体。
2、Runtime消息传递
一个对象的方法像这样[obj foo]
,编译器转成消息发送objc_msgSend(obj, foo)
,Runtime
时执行的流程是这样的:
-
首先,通过
obj
的isa
指针找到它的class
; -
在
class
的method list
找foo
; -
如果
class
中没到foo
,继续往它的superclass
中找 ; -
一旦找到
foo
这个函数,就去执行它的实现IMP
。
但这种实现有个问题,效率低。但一个class
往往只有 20%
的函数会被经常调用,可能占总调用次数的 80%
。每个消息都需要遍历一次objc_method_list
并不合理。如果把经常被调用的函数缓存下来,那可以大大提高函数查询的效率。这也就是objc_class
中另一个重要成员objc_cache
做的事情 - 再找到foo
之后,把foo
的method_name
作为key
,method_imp
作为value
给存起来。当再次收到foo
消息的时候,可以直接在cache
里找到,避免去遍历objc_method_list
。从前面的源代码可以看到objc_cache
是存在objc_class
结构体中的。
objec_msgSend的方法定义如下:
OBJC_EXPORT id objc_msgSend(id self, SEL op, ...)
那消息传递是怎么实现的呢?我们看看对象(object),类(class),方法(method)这几个的结构体:
//对象
struct objc_object {
Class isa OBJC_ISA_AVAILABILITY;
};
//类
struct objc_class {
Class isa OBJC_ISA_AVAILABILITY;
#if !__OBJC2__
Class super_class OBJC2_UNAVAILABLE;
const char *name OBJC2_UNAVAILABLE;
long version OBJC2_UNAVAILABLE;
long info OBJC2_UNAVAILABLE;
long instance_size OBJC2_UNAVAILABLE;
struct objc_ivar_list *ivars OBJC2_UNAVAILABLE;
struct objc_method_list **methodLists OBJC2_UNAVAILABLE;
struct objc_cache *cache OBJC2_UNAVAILABLE;
struct objc_protocol_list *protocols OBJC2_UNAVAILABLE;
#endif
} OBJC2_UNAVAILABLE;
//方法列表
struct objc_method_list {
struct objc_method_list *obsolete OBJC2_UNAVAILABLE;
int method_count OBJC2_UNAVAILABLE;
#ifdef __LP64__
int space OBJC2_UNAVAILABLE;
#endif
/* variable length structure */
struct objc_method method_list[1] OBJC2_UNAVAILABLE;
} OBJC2_UNAVAILABLE;
//方法
struct objc_method {
SEL method_name OBJC2_UNAVAILABLE;
char *method_types OBJC2_UNAVAILABLE;
IMP method_imp OBJC2_UNAVAILABLE;
}
-
系统首先找到消息的接收对象,然后通过对象的
isa
找到它的类。 -
在它的类中查找
method_list
,是否有selector
方法。 -
没有则查找父类的
method_list
。 -
找到对应的
method
,执行它的IMP
。 -
转发
IMP
的return
值。
下面讲讲消息传递用到的一些概念:
-
类对象(objc_class)
-
实例(objc_object)
-
元类(Meta Class)
-
Method(objc_method)
-
SEL(objc_selector)
-
IMP
-
类缓存(objc_cache)
-
Category(objc_category)
2.1 类对象(objc_class)
Objective-C
类是由Class
类型来表示的,它实际上是一个指向objc_class
结构体的指针。
typedef struct objc_class *Class;
查看objc/runtime.h
中objc_class
结构体的定义如下:
struct objc_class {
Class _Nonnull isa OBJC_ISA_AVAILABILITY;
#if !__OBJC2__
Class _Nullable super_class OBJC2_UNAVAILABLE;
const char * _Nonnull name OBJC2_UNAVAILABLE;
long version OBJC2_UNAVAILABLE;
long info OBJC2_UNAVAILABLE;
long instance_size OBJC2_UNAVAILABLE;
struct objc_ivar_list * _Nu