Objective-C runtime 消息传递与转发
栏目: Objective-C · 发布时间: 7年前
内容简介:Objective-C 本质上是一种基于 C 语言的领域特定语言。Objective-C 通过一个用 C 语言和汇编实现的 runtime,在 C 语言的基础上实现了面向对象的功能。在 runtime 中,对象用结构体表示,方法用函数表示。C 语言是一门静态语言,其在编译时决定调用哪个函数。而 Objective-C 则是一门动态语言,其在编译时不能决定最终执行时调用哪个函数(Objective-C 中函数调用称为消息传递)。Objective-C 的这种动态绑定机制正是通过 runtime 这样一个中间
Objective-C 本质上是一种基于 C 语言的领域特定语言。Objective-C 通过一个用 C 语言和汇编实现的 runtime,在 C 语言的基础上实现了面向对象的功能。在 runtime 中,对象用结构体表示,方法用函数表示。
C 语言是一门静态语言,其在编译时决定调用哪个函数。而 Objective-C 则是一门动态语言,其在编译时不能决定最终执行时调用哪个函数(Objective-C 中函数调用称为消息传递)。Objective-C 的这种动态绑定机制正是通过 runtime 这样一个中间层实现的。
为了分析 runtime 是如何进行动态绑定,我们首先需要了解一下 Objective-C 中类与对象等基本结构在 C 语言层面是如何实现的。
数据结构
Objective-C 类
Objective-C 类是由 Class 类型表示的,它本质上是一个指向 objc_class 结构体的指针。如下所示为 objc/runtime.h 中关于类的定义:
typedef struct object_class *Class
struct object_class{
Class isa OBJC_ISA_AVAILABILITY;
#if !__OBJC2__
Class super_class OBJC2_UNAVAILABLE; // 父类
const char *name OBJC2_UNAVAILABLE; // 类名
long version OBJC2_UNAVAILABLE; // 类的版本信息,默认为0
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;
/* Use `Class` instead of `struct objc_class *` */
Objective-C 对象
Objective-C 对象是由 id 类型表示的,它本质上是一个指向 objc_object 结构体的指针。如下所示为 objc/objc.h 中关于对象的定义:
typedef struct objc_object *id;
struct objc_object{
Class isa OBJC_ISA_AVAILABILITY;
};
objc_object 结构体中只有一个成员,即指向其类的 isa 指针。 当向一个 Objective-C 对象发送消息时,runtime 会根据实例对象的 isa 指针找到其所属的类。Runtime 会在类的方法列表以及父类的方法列表中去寻找与消息对应的 selector 指向的方法,找到后即运行该方法。
Objective-C 元类(meta class)
meta-class 是一个 类对象的类 。在 Objective-C 中,所有的类本身也是一个对象。事实上,在很多原型编程语言也采用这种“万物皆对象”的设计思想,如:Io。
通过向该对象发送消息,即可实现对类方法的调用。前提是类的 isa 指针必须指向一个包含这些类方法的 objc_class 结构体。 meta-class 中存储着一个类的所有类方法。所以,类对象的 isa 指针指向的就是 meta-class 。
- 当向一个对象发送消息时,runtime 会在这个对象所属的类的方法列表中查找方法。
- 当向一个类发送消息时,runtime 会在这个类的 meta-class 的方法列表中查找。
思考一下, meta-class 也是一个类,也可以向它发送一个消息,那么它的 isa 又是指向什么呢?为了不让这种结构无限延伸下去,Objective-C 的设计者让所有的 meta-class 的 isa 指向基类的 meta-class ,以此作为它们的所属类。
下图所示,为 Objective-C 对象在内存中的引用关系图。
Objective-C 方法
方法实际上是一个指向 objc_method 结构体的指针。如下所示为 objc/runtime.h 中关于方法的定义:
typedef struct objc_method *Method
struct objc_method{
SEL method_name OBJC2_UNAVAILABLE; // 方法名
char *method_types OBJC2_UNAVAILABLE;
IMP method_imp OBJC2_UNAVAILABLE; // 方法实现
}
结构体中包含成员 SEL 和 IMP ,两者将方法的名字与实现进行了绑定。通过 SEL ,可以找到对应的 IMP ,从而调用方法的具体实现。
SEL
SEL 又称选择器,是一个指向 objc_selector 结构体的指针。
typedef struct objc_selector *SEL;
方法的 selector 用于表示运行时方法的名字。 Objective-C 在编译时,会根据方法的名字(不包括参数)生成一个唯一的整型标识( Int 类型的地址),即 SEL 。
由于一个类的方法列表中不能存在两个相同的 SEL ,所以 Objective-C 不支持重载。但是不同类之间可以存在相同的 SEL ,因为不同类的实例对象执行相同的 selector 时,会在各自的方法列表中去根据 selector 去寻找自己对应的 IMP 。
通过下面三种方法可以获取 SEL :
sel_registerName @selector() NSSeletorFromString()
IMP
IMP 本质上就是一个函数指针,指向方法实现的地址。
typedef id (*IMP)(id, SEL,...);
参数说明
-
id:指向self的指针(如果是实例方法,则是类实例的内存地址;如果是类方法,则是指向元类的指针) -
SEL:方法选择器 -
...:方法的参数列表
SEL 与 IMP 的关系类似于哈希表中 key 与 value 的关系。采用这种哈希映射的方式可以加快方法的查找速度。
消息传递(方法调用)
在 Objective-C 中, 消息直到运行时才绑定到方法实现上 。编译器会将消息表达式 [receiver message] 转化为一个消息函数的调用,即 objc_msgSend 。这个函数将消息接收者和方法名作为主要参数,如下所示:
objc_msgSend(receiver, selector) // 不带参数 objc_msgSend(receiver, selector, arg1, arg2,...) // 带参数
objc_msgSend 通过以下几个步骤实现了动态绑定机制。
- 首先,获取
selector指向的方法实现。由于相同的方法可能在不同的类中有着不同的实现,因此根据receiver所属的类进行判断。 - 其次,传递
receiver对象、方法指定的参数来调用方法实现。 - 最后,返回方法实现的返回值。
消息传递的关键在于前文讨论过的 objc_class 结构体,其有两个关键的字段:
-
isa:指向父类的指针 -
methodLists: 类的方法分发表(dispatch table)
当创建一个新对象时,先为其分配内存,并初始化其成员变量。其中 isa 指针也会被初始化,让对象可以访问类及类的继承链。
下图所示为消息传递过程的示意图。
- 当消息传递给一个对象时,首先从运行时系统缓存
objc_cache中进行查找。如果找到,则执行。否则,继续执行下面步骤。 -
objc_msgSend通过对象的isa指针获取到类的结构体,然后在方法分发表methodLists中查找方法的selector。如果未找到,将沿着类的isa找到其父类,并在父类的分发表methodLists中继续查找。 - 以此类推,一直沿着类的继承链追溯至
NSObject类。一旦找到selector,传入相应的参数来执行方法的具体实现,并将该方法加入缓存objc_cache。如果最后仍然没有找到selector,则会进入 消息转发 流程(下文将进行介绍)。
消息转发
当一个对象能接收一个消息时,会走正常的消息传递流程。当一个对象无法接收某一消息时,会发生什么呢?默认情况下,如果以 [object message] 的形式调用方法,如果 object 无法响应 message 消息时,编译器会报错。如果是以 performSeletor: 的形式调用方法,则需要等到运行时才能确定 object 是否能接收 message 消息。如果不能,则程序崩溃。
对于后者,当不确定一个对象是否能接收某个消息时,可以调用 respondsToSelector: 来进行判断。
if ([self respondsToSelector:@selector(method)]) {
[self performSelector:@selector(method)];
}
事实上,当一个对象无法接收某一消息时,就会启动所谓“消息转发(message forwarding)”机制。通过消息转发机制,我们可以告诉对象如何处理未知的消息。
消息转发机制大致可分为三个步骤:
- 动态方法解析(Dynamic Method Resolution)
- 备用接收者
- 完整消息转发
下图所示为消息转发过程的示意图。
动态消息解析
对象在接收到未知的消息时,首先会调用所属类的类方法 +resolveClassMethod: 或实例方法 +resolveInstanceMethod: 。
在这两个方法中,我们可以为未知消息新增一个“处理方法”,通过运行时 class_addMethod 函数动态添加到类中。比如:
void dynamicMethodIMP(id self, SEL _cmd) {
// 方法实现
}
+ (BOOL)resolveInstanceMethod:(SEL)aSEL {
if (aSEL == @selector(resolveThisMethodDynamically)) {
class_addMethod([self class], aSEL, (IMP) dynamicMethodIMP, "v@:");
return YES;
}
return [super resolveInstanceMethod:aSEL];
}
这种方案更多的是为了实现@dynamic属性。
备用接收者
如果在上一步无法处理消息,则 runtime 会继续调用 - (id)forwardingTargetForSelector:(SEL)aSelector 方法。
如果一个对象实现了这个方法,并返回一个非 nil (也不能是 self ) 的对象,则这个对象会称为消息的新接收者,消息会被分发到这个对象。比如:
- (id)forwardingTargetForSelector:(SEL)aSelector {
NSString * selString = NSStringFromSelector(aSelector);
if ([selString isEqualToString:@"walk"]) {
return self.otherObject;
}
return [super forwardingTargetForSelector:aSelector];
}
这一步合适于我们只想将消息转发到另一个能处理该消息的对象上。但这一步无法对消息进行处理,如操作消息的参数和返回值。
完整消息转发
如果在上一步还不能处理未知消息,则唯一能做的就是启用完整的消息转发机制了。
这步调用 methodSignatureForSelector 进行方法签名,这可以将函数的参数类型和返回值封装。如果返回 nil ,则说明消息无法处理并报错 unrecognized selector sent to instance 。
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
if ([NSStringFromSelector(aSelector) isEqualToString:@"testInstanceMethod"]){
return [NSMethodSignature signatureWithObjcTypes:"v@:"];
}
return [super methodSignatureForSelector: aSelector];
}
如果返回 methodSignature ,则进入 forwardInvocation 。对象会创建一个表示消息的 NSInvocation 对象,把与尚未处理的消息有关的全部细节都封装在 anInvocation 中,包括 selector , target ,参数。在这个方法中可以修改实现方法,修改响应对象等,如果方法调用成功,则结束。如果依然不能正确响应消息,则报错 unrecognized selector sent to instance 。
- (void)forwardInvovation:(NSInvocation)anInvocation {
[anInvocation invokeWithTarget:_helper];
[anInvocation setSelector:@selector(run)];
[anInvocation invokeWithTarget:self];
}
可以利用备用接受者和完整消息转发实现对接受消息对象的转移,可以实现“多重继承”的效果。
参考
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
HTTP Essentials
Stephen A. Thomas、Stephen Thomas / Wiley / 2001-03-08 / USD 34.99
The first complete reference guide to the essential Web protocol As applications and services converge and Web technologies not only assume HTTP but require developers to manipulate it, it is be......一起来看看 《HTTP Essentials》 这本书的介绍吧!
随机密码生成器
多种字符组合密码
RGB CMYK 转换工具
RGB CMYK 互转工具