面试题引发的思考:
Q: 简述消息转发机制?
- 消息发送阶段:负责从 类及父类 的 缓存列表及方法列表 查找方法;
- 动态解析阶段:如果消息发送阶段没有找到方法,则会进入动态解析阶段,负责 动态的添加 方法实现;
- 消息转发阶段:如果也没有实现动态解析方法,则会进行消息转发阶段,将消息 转发 给可以处理消息的 接收者 来处理;
- 报错:如果也没有实现消息转发方法,会报错
unrecognzied selector sent to instance
。
1. 方法调用的本质
前两章分别对isa
结构的本质、Class
结构的本质做了探究,下面探究方法调用的本质。
// TODO: ----------------- main -----------------
int main(int argc, const char * argv[]) {
@autoreleasepool {
Person *person = [[Person alloc] init];
[person test];
}
return 0;
}
转成C++代码:
由以上代码可知:
OC的方法调用都是转化为objc_msgSend()
函数,即消息机制,通过Runtime给方法调用者发送消息。
而objc_msgSend(person, @selector(test));
的作用是给消息接收者person
发送test
消息。
那么接下来我们探究objc_msgSend()
函数的调用过程:
objc_msgSend()
的执行流程可以分为三个阶段:
- 消息发送阶段:负责从类及父类的缓存列表及方法列表查找方法;
- 动态解析阶段:如果消息发送阶段没有找到方法,则会进入动态解析阶段,负责动态的添加方法实现;
- 消息转发阶段:如果也没有实现动态解析方法,则会进行消息转发阶段,将消息转发给可以处理消息的接收者来处理;
- 报错:如果也没有实现消息转发方法,会报错
unrecognzied selector sent to instance
。
接下来通过源码探寻以上三个阶段的实现。
(1) 消息发送阶段
OC源码中搜索_objc_msgSend
,在objc-msg-arm64.s
汇编文件找到_objc_msgSend
函数的实现:
判断消息接收者是否为nil
:
如果为nil
,直接退出程序;
否则继续执行代码至CacheLookup NORMAL
,去缓存查找:
判断缓存中找到对应的方法:
如果可以找到,执行CacheHit $0
,进而调用方法;
否则执行CheckMiss $0
:
如果缓存中没有找到对应方法,执行__objc_msgSend_uncached
函数:
执行MethodTableLookup
即方法列表查找:
内部核心代码为__class_lookupMethodAndLoadCache3
函数:
汇编的函数比C++的多一个下划线,所以查找_class_lookupMethodAndLoadCache3
即可:
以上步骤进流程图如下:
下面对lookUpImpOrForward
函数进行分析:
通过getMethodNoSuper_nolock
函数在类的方法列表查找方法:
通过getMethodNoSuper_nolock
函数遍历得到类对象的方法列表,然后通过search_method_list
函数查找方法:
如果方法列表是有序的,通过二分查找方法;
否则遍历列表查找方法。
二分查找实现原理如下:
以上步骤进流程图如下:
如果消息发送阶段没有找到方法,就会进入动态解析阶段。
(2) 动态解析阶段
由以上代码可知:
如果消息发送阶段没有找到方法,就会进入动态解析阶段;
动态解析方法之后,triedResolver = YES;
,然后goto retry
,那么会重新查找一遍方法,并跳过动态解析阶段。
动态解析阶段主要函数为_class_resolveMethod
:
_class_resolveMethod
函数会根据类对象或者元类对象,分别调用resolveInstanceMethod
方法或resolveClassMethod
方法。
动态解析进流程图如下:
动态解析过程实例
a> 实例方法动态解析
// TODO: ----------------- Person类 -----------------
@interface Person : NSObject
- (void)test;
@end
@implementation Person
@end
// TODO: ----------------- main -----------------
int main(int argc, const char * argv[]) {
@autoreleasepool {
Person *person = [[Person alloc] init];
[person test];
}
return 0;
}
以上代码运行报错:unrecognized selector sent to instance 0x1006088b0
,因为在Person
类中声明了test
方法,却并没有实现。
接下来通过动态解析解决问题,相关API为:
// TODO: ----------------- Person类 -----------------
@interface Person : NSObject
- (void)test;
@end
@implementation Person
+ (BOOL)resolveInstanceMethod:(SEL)sel {
if (sel == @selector(test)) {
// 获取其他方法
Method otherMethod = class_getInstanceMethod(self, @selector(other));
// 动态添加方法
class_addMethod(self, sel, method_getImplementation(otherMethod), method_getTypeEncoding(otherMethod));
// 返回YES表示有动态添加方法
return YES;
}
return [super resolveInstanceMethod:sel];
};
- (void)other {
NSLog(@"%s", __func__);
}
@end
// 打印结果
Demo[1234:567890] -[Person other]
由打印结果可知:
person
在调用test
方法时经过动态解析成功调用了other
方法。
b> 类方法动态解析
// TODO: ----------------- Person类 -----------------
@interface Person : NSObject
+ (void)classTest;
@end
@implementation Person
+ (BOOL)resolveClassMethod:(SEL)sel {
if (sel == @selector(classTest)) {
// 获取其他方法
Method otherMethod = class_getClassMethod(self, @selector(classOther));
// 动态添加方法
class_addMethod(object_getClass(self), sel, method_getImplementation(otherMethod), method_getTypeEncoding(otherMethod));
// 返回YES表示有动态添加方法
return YES;
}
return [super resolveClassMethod:sel];
}
+ (void)classOther {
NSLog(@"%s", __func__);
}
@end
// TODO: ----------------- main -----------------
int main(int argc, const char * argv[]) {
@autoreleasepool {
[Person classTest];
}
return 0;
}
(3) 消息转发阶段
如果消息发送阶段没有找到方法,动态解析阶段没有对方法进行动态解析,接下来就会进入消息转发阶段。
消息转发阶段,会调用_objc_msgForward_impcache
函数:
汇编中找到__objc_msgForward_impcache
函数实现,该函数调用__objc_msgForward
函数;
__objc_msgForward
函数实现调用__objc_forward_handler
函数;
无法找到__objc_forward_handler
函数实现,查找资料了解到消息转发机制是不开源的。
消息转发过程实例
a> 实例方法消息转发
// TODO: ----------------- Person类 -----------------
@interface Person : NSObject
- (void)test;
@end
@implementation Person
@end
// TODO: ----------------- main -----------------
int main(int argc, const char * argv[]) {
@autoreleasepool {
Person *person = [[Person alloc] init];
[person test];
}
return 0;
}
由以上代码可知:
Person
类中只声明test
方法,不实现方法,此时调用test
方法会崩溃。
在Student
类中实现一个test
方法,在Person
类中通过forwardingTargetForSelector:
方法将消息转发对象设置为Student
对象:
// TODO: ----------------- Person类 -----------------
@interface Person : NSObject
- (void)test;
@end
@implementation Person
/**
消息转发
@param aSelector 方法
@return 返回能够处理消息的对象
*/
- (id)forwardingTargetForSelector:(SEL)aSelector {
if (aSelector == @selector(test)) {
return [[Student alloc] init];
}
return [super forwardingTargetForSelector:aSelector];
}
@end
// TODO: ----------------- Student类 -----------------
@interface Student : NSObject
@end
@implementation Student
- (void)test {
NSLog(@"%s", __func__);
}
@end
// TODO: ----------------- main -----------------
int main(int argc, const char * argv[]) {
@autoreleasepool {
Person *person = [[Person alloc] init];
[person test];
}
return 0;
}
// 打印结果
// Runtime[939:123812] -[Student test]
如果forwardingTargetForSelector
函数返回为nil
或者没有实现的话,就会调用methodSignatureForSelector
函数,用来返回一个方法签名;
然后调用forwardInvocation
函数,其内部参数为NSInvocation
类型,NSInvocation
类型封装一个方法的调用,包括方法调用者、方法、方法的参数;
然后在forwardInvocation
函数内修改方法调用对象。
// TODO: ----------------- Person类 -----------------
@interface Person : NSObject
- (void)test;
@end
@implementation Person
/**
消息转发
@param aSelector 方法
@return 返回能够处理消息的对象
*/
- (id)forwardingTargetForSelector:(SEL)aSelector {
if (aSelector == @selector(test)) {
// 返回nil则会调用methodSignatureForSelector方法
return nil;
}
return [super forwardingTargetForSelector:aSelector];
}
/**
为另一个类实现的消息创建一个有效的方法签名
@param aSelector 方法
@return 方法签名:返回值类型、参数类型
*/
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
if (aSelector == @selector(test)) {
return [[[Student alloc] init] methodSignatureForSelector:aSelector];
}
return [super methodSignatureForSelector:aSelector];
}
/**
将选择器转发给一个真正实现了该消息的对象
@param anInvocation 封装了一个方法调用,包括:方法调用者,方法,方法的参数
@param anInvocation.target 方法调用者
@param anInvocation.selector 方法
@param [anInvocation getArgument: NULL atIndex: 0]; 方法的参数
*/
- (void)forwardInvocation:(NSInvocation *)anInvocation {
// 此时anInvocation.target 还是person对象,需要修改target为可以执行方法的方法调用者。
[anInvocation invokeWithTarget:[[Student alloc] init]];
}
@end
// 打印结果
// Runtime[1016:190175] -[Student test]
消息转发流程图如下:
b> 类方法消息转发
// TODO: ----------------- Person类 -----------------
@interface Person : NSObject
+ (void)test;
@end
@implementation Person
+ (id)forwardingTargetForSelector:(SEL)aSelector {
if (aSelector == @selector(test)) {
return [Student class];
}
return [super forwardingTargetForSelector:aSelector];
}
@end
// TODO: ----------------- Student类 -----------------
@interface Student : NSObject
@end
@implementation Student
+ (void)test {
NSLog(@"%s", __func__);
}
@end
// TODO: ----------------- main -----------------
int main(int argc, const char * argv[]) {
@autoreleasepool {
[Person test];
}
return 0;
}
// 打印结果
// Runtime[1245:167577] +[Student test]
如果forwardingTargetForSelector
函数返回为nil
或者没有实现的话:
// TODO: ----------------- Person类 -----------------
@interface Person : NSObject
- (void)test;
@end
@implementation Person
+ (id)forwardingTargetForSelector:(SEL)aSelector {
if (aSelector == @selector(test)) {
return nil;
}
return [super forwardingTargetForSelector:aSelector];
}
+ (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
if (aSelector == @selector(test)) {
return [[Student class] methodSignatureForSelector:aSelector];
}
return [super methodSignatureForSelector:aSelector];
}
+ (void)forwardInvocation:(NSInvocation *)anInvocation {
[anInvocation invokeWithTarget:[Student class]];
}
@end
// 打印结果
// Runtime[1335:187977] +[Student test]
(4) 总结
- OC的方法调用都是转化为
objc_msgSend()
函数,即消息机制,通过Runtime给方法调用者发送消息。- 方法调用分为三个阶段:
消息发送、动态方法解析、消息转发。
以上三个阶段的具体分析以及流程图可从上文得知。