在Objective-c语言中,调用某个对象的方法,称为向某个对象发送消息。不同于C语言的函数调用,OC的方法调用是动态的,在运行时才决定调用具体的方法实现。
int main(int argc, const char * argv[])
{
@autoreleasepool {
NSString * str = [[NSString alloc] init];
NSLog(@"%@",str);
}
return 0;
}
将以上的OC代码通过clang -rewrite-objc转换为C/C++语言
int main(int argc, const char * argv[])
{
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
NSString * str = ((NSString *(*)(id, SEL))(void *)objc_msgSend)((id)((NSString *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSString"), sel_registerName("alloc")), sel_registerName("init"));
NSLog((NSString *)&__NSConstantStringImpl__var_folders_32_bt767lwx1tvdwp4g5l1b3w2w0000gp_T_main_c3e70f_mi_0,str);
}
return 0;
}
可以看到OC代码中+alloc和-init的方法调用的部分,转变为objc_msgSend()的函数调用
C语言的函数调用
#include <stdio.h>
void printHello(){
printf("Hello World");
}
void printGoodBye(){
printf("Good Bye");
}
void test(int a){
if(a > 0) {
printHello();
} else {
printGoodBye();
}
}
C 语言在编译期就已经知道test()函数中会调用printHello()和printGoodBye()两个函数,会直接生成调用这些函数的指令,函数的入口地址会以硬编码的方法编译在指令中,这叫做静态绑定。
#include <stdio.h>
void printHello(){
printf("Hello World");
}
void printGoodBye(){
printf("Good Bye");
}
void test1(int a){
void(*func)();
if(a > 0) {
func = printHello;
} else {
func = printGoodBye;
}
func();
}
在编译期,test1()中会生成调用func函数的指令,但是func指向哪个具体的函数需要在运行期才能够确定,因此无法将函数入口地址硬编码到指令中。这就叫做动态绑定。
OC的方法方法调用就属于动态绑定,在编译期硬编码到指令中的是objc_msgSend()的函数调用,而调用的具体方法实现在运行期确定。
消息发送 objc_msgSend()
objc_msgSend() 函数实现的伪代码大致如下:先确定对象的类,再确定方法的具体实现并调用
id objc_msgSend(id self, SEL _cmd, ...) {
Class class = object_getClass(self);
IMP imp = class_getMethodImplementation(class, _cmd);
return imp ? imp(self, _cmd, ...) : 0;
}
IMP class_getMethodImplementation(Class cls, SEL sel)
{
IMP imp;
if (!cls || !sel) return nil;
imp = lookUpImpOrNil(cls, sel, nil, YES/*initialize*/, YES/*cache*/, YES/*resolver*/);
// Translate forwarding function to C-callable external version
if (!imp) {
return _objc_msgForward;
}
return imp;
}
class_getMethodImplementation 的大致实现:
-
首先会查询
fast map快速映射表,如果找到则返回,未找到进入步骤2 (每一个类都有一块fast map的缓存,保存之前的匹配结果) -
查询接受者所属类的
方法列表,如果找到则返回,未找到进入步骤3 -
沿着
继承体系继续向上查找,如果找到则返回,未找到进入消息转发的流程
消息转发
当一个OC对象收到一条消息,OC对象会在所属类的fast map,方法列表以及在继承树中查询是否有对应的方法实现。如果能够找到,则直接调用方法实现;如果没有找到,则进入消息转发的流程。消息转发可以理解为在运行时发现消息没有对应的方法实现时,进行动态补救的过程。
动态方法解析(dynamic method resolution)
对象在收到无法解读的消息后,首先会进行动态方法解析,涉及以下两个方法:
@interface NSObject<NSObject>
+ (BOOL)resolveClassMethod:(SEL)sel OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);
+ (BOOL)resolveInstanceMethod:(SEL)sel OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);
@end
+resolveInstanceMethod:在无法找到实例方法实现时调用;
+resolveClassMethod:在无法找到类方法实现时调用;
在这两个方法中可以在运行时为当前类添加sel的方法实现,前提是方法实现的代码在编译时已经存在。
// 例子
id dynamicGetter(id obj, SEL selector){
return @"dynamicGetter";
}
void dynamicSetter(id obj, SEL selector,id value){
NSLog(@"dynamicSetter %@",value);
}
@interface Test : NSObject
@property(nonatomic,strong) NSString * property1;
@end
@implementation Test
@dynamic property1; // @dynamic 修饰属性,属性不生成对应的setter和getter方法
+ (BOOL)resolveInstanceMethod:(SEL)sel {
NSString * selStr = NSStringFromSelector(sel);
if([selStr isEqualsToString:@"setProperty1:"]){
class_addMethod(self, sel, (IMP)dynamicSetter, "v@:");
return YES;
} else if([selStr isEqualsToString:@"property1"]) {
class_addMethod(self, sel, (IMP)dynamicGetter, "@@:");
return YES;
}
return [super resolveInstanceMethod:sel]; // 子类无法处理时,记得调用父类的resolveInstanceMethod
}
@end
在以上代码中,
Test类声明了property1属性,但是用@dynamic修饰了property1属性,property1不会生成对应的setter和getter方法。
当试图调用
property1的setter或者getter方法,在Test类的方法列表和继承树中都找不到对应的方法实现,就进入动态方法解析,调用+resolveInstanceMethod:。在这个方法中为property1的setter或者getter方法添加方法实现,方法实现是已经存在的函数。
+resolveInstanceMethod:返回YES后,就重新走一遍消息发送的流程,此时property1的setter或者getter方法已经有了方法实现; 返回 NO 后,则继续消息转发的流程
-forwardingTargetForSelector:
当动态方法解析无法处理未知消息,就会调用对象 -forwardingTargetForSelector:,试图将消息完整的转发给另一个对象,在方法中无法修改消息的内容(selector 和 参数)。
// 例子
@interface Test1 : NSObject
@property(nonatomic,strong) NSString * property1;
@end
@implementation Test1
@end
@interface Test : NSObject
@property(nonatomic,strong) NSString * property1;
@property(nonatomic,strong) Test1* test1;
@end
@implementation Test
@dynamic property1; // @dynamic 修饰属性,属性不生成对应的setter和getter方法
- (id)forwardingTargetForSelector:(SEL)selector {
if([selStr isEqualsToString:@"setProperty1:"] ||
[selStr isEqualsToString:@"property1"]) {
return _test1;
}
return [super forwardingTargetForSelector:selector]; // 子类无法处理时,记得调用父类的forwardingTargetForSelector
}
@end
在以上代码中,当调用
Test的property1的setter或者getter方法,找不到方法实现,动态方法解析没有处理时,调用到-forwardingTargetForSelector:方法,在这里将消息完整转发给Test1类的实例。
注意: -forwardingTargetForSelector:不能返回self,否则会陷入无限循环
-forwardInvocation:
如果-forwardingTargetForSelector:不能够处理消息,就会调用到-forwardInvocation:
- (void)forwardInvocation:(NSInvocation *)anInvocation OBJC_SWIFT_UNAVAILABLE("");
如果转发算法到了这一步,会启动完整的消息转发机制。首先会创建NSInvocation对象,NSInvocation对象会将receiver,selector以及参数都封装起来,并作为参数传递到-forwardInvocation:方法。而在-forwardInvocation:方法中可以修改receiver,selector以及参数,使得NSInvocation成为一次有效的调用。
实现-forwardInvocation:方法时,若发现某调用操作不应由本类处理,则需要调用父类的同名方法。这样继承体系中每个类都有机会处理此调用请求,直到NSObject。在NSObject的-forwardInvocation:方法中,还会继而调用doseNotRecognizerSelector:抛出异常,表明消息最终未能处理。
消息转发的全流程

在上图中, 接收者在每一步中均有机会处理消息。步骤越往后,处理消息的代价就越大。最好能够在第一步就处理完,这样运行期系统就可以将此方法缓存起来。如果此类的实例收到同名的消息,那么根本无需启动消息转发流程。若想在第三步将消息转给备用的接受者,那么建议还是在第二步进行转发,第三步的代价会比第二步大得多。
总结
-
Objective-C的方法调用成为
消息发送,消息由receiver,selector以及参数组成。 -
Objective-C的消息发送是
动态绑定的,在运行期确定方法实现。 -
Objective-C的消息发送实际上调用的是
msg_send()函数,在类的fastmap,方法列表以及继承体系中寻找方法实现。 -
如果某次方法调用无法找到方法实现,就进入
消息转发的流程。 -
消息转发首先进行动态方法解析,调用
+resolveInstanceMethod:或者+resolveClassMethod:,试图为消息添加对应的方法实现。 -
-forwardingTargetForSelector:会完整的转发消息,不会修改消息的selector和参数。 -
经过以上两步还不能处理消息,就调用
-forwardInvocation:启动完整的消息转发机制,可以任意修改消息的receiver,selector以及参数。
参考文档
这里贴上大神的消息发送和消息转发的流程图:

