OC底层消息转发机制
1. 前言
上一篇文章(OC底层方法的本质、查找流程)主要说了方法的查找流程,但是方法最后找到了NSObject都没有对应的方法实现,就直接崩溃吗?当然不是的,苹果还给我们提供一个消息转发的机制,下面咱们来具体看一下。
作为一个开发者,有一个学习的氛围跟一个交流圈子特别重要,这是一个我的iOS交流群:196800191,加群密码:112233,不管你是小白还是大牛欢迎入驻 ,分享BAT,阿里面试题、面试经验,讨论技术, 大家一起交流学习成长!
2. 动态方法决议
在上一篇文章介绍的查找IMP的方法中,当找不到的时候,会执行一次_class_resolveMethod方法,具体实现如下(详细解释见注释):
/***********************************************************************
* _class_resolveMethod
* Call +resolveClassMethod or +resolveInstanceMethod.
* Returns nothing; any result would be potentially out-of-date already.
* Does not check if the method already exists.
**********************************************************************/
void _class_resolveMethod(Class cls, SEL sel, id inst)
{
/**
判断当前传入的类是否是元类。
如果是元类,则走类方法的动态决议。
如果不是元类,即普通的类,则走实例方法的动态决议。
*/
if (! cls->isMetaClass()) {
// try [cls resolveInstanceMethod:sel]
// 实例方法动态决议
_class_resolveInstanceMethod(cls, sel, inst);
}
else {
// try [nonMetaClass resolveClassMethod:sel]
// and [cls resolveInstanceMethod:sel]
// 类方法的动态决议
_class_resolveClassMethod(cls, sel, inst);
/**
此处的if判断可谓重点。
经过上面_class_resolveClassMethod方法后,有可能还没有找到一个方法的IMP。
此时判断一下,是否有可以实现的类方法的IMP。
如果有,该if方法不进。
如果没有,则再走一遍_class_resolveInstanceMethod方法,调用NSObject的resolveInstanceMethod方法,为什么这么做呢,因为根据之前文章介绍的isa走位图可知,根元类是继承NSObject的,同时拥有NSObject所有的方法,所以再给一次调用resolveInstanceMethod机会,来处理这个类方法。
*/
if (!lookUpImpOrNil(cls, sel, inst,
NO/*initialize*/, YES/*cache*/, NO/*resolver*/))
{
_class_resolveInstanceMethod(cls, sel, inst);
}
}
}
上面的方法则是一个统一的调用入口,下面分别看一下。
2.1 实例方法动态决议
实例方法动态决议方法如下:
/***********************************************************************
* _class_resolveInstanceMethod
* Call +resolveInstanceMethod, looking for a method to be added to class cls.
* cls may be a metaclass or a non-meta class.
* Does not check if the method already exists.
**********************************************************************/
static void _class_resolveInstanceMethod(Class cls, SEL sel, id inst)
{
/**
此判断会去查找当前类以及父类等是否实现了resolveInstanceMethod方法。
如果没有实现,则直接return,因为if下面的逻辑即是向该方法发送消息,苹果不可能让自己的程序崩溃的。
其实NSObject类已经实现了这个方法,具体见下面,那么老祖宗都实现了,这个判断是否多余呢?如果你写的类不继承NSObject呢,是不是就没有默认实现了呢。
如果有实现,则继续走下面的逻辑。
*/
if (! lookUpImpOrNil(cls->ISA(), SEL_resolveInstanceMethod, cls,
NO/*initialize*/, YES/*cache*/, NO/*resolver*/))
{
// Resolver not implemented.
return;
}
// 创建消息,给类发送resolveInstanceMethod消息,此时本类的resolveInstanceMethod方法会被调用,如果没实现,那么调用父类的该方法。
BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
bool resolved = msg(cls, SEL_resolveInstanceMethod, sel);
// Cache the result (good or bad) so the resolver doesn't fire next time.
// +resolveInstanceMethod adds to self a.k.a. cls
IMP imp = lookUpImpOrNil(cls, sel, inst,
NO/*initialize*/, YES/*cache*/, NO/*resolver*/);
if (resolved && PrintResolving) {
if (imp) {
_objc_inform("RESOLVE: method %c[%s %s] "
"dynamically resolved to %p",
cls->isMetaClass() ? '+' : '-',
cls->nameForLogging(), sel_getName(sel), imp);
}
else {
// Method resolver didn't add anything?
_objc_inform("RESOLVE: +[%s resolveInstanceMethod:%s] returned YES"
", but no new implementation of %c[%s %s] was found",
cls->nameForLogging(), sel_getName(sel),
cls->isMetaClass() ? '+' : '-',
cls->nameForLogging(), sel_getName(sel));
}
}
}
下面看一下NSObject类的resolveInstanceMethod方法实现:
+ (BOOL)resolveInstanceMethod:(SEL)sel {
return NO;
}
那么当我们实现的这个方法被调用了,都可以做什么呢?来看下面一段代码:
@interface GYMPerson : NSObject
- (void)playGame;
- (void)sleep;
@end
@implementation GYMPerson
- (void)sleep {
NSLog(@"%s 调用了", __func__);
}
+ (BOOL)resolveInstanceMethod:(SEL)sel {
return NO;
}
@end
上面有一个GYMPerson类,有两个实例方法,其中playGame只定义,没有具体实现, sleep既定义又实现,然后咱们开始调用playGame方法。
int main(int argc, const char * argv[]) {
@autoreleasepool {
GYMPerson *person = [GYMPerson alloc];
[person playGame];
}
return 0;
}
当调用playGame方法后,程序直接挂掉了,报错如下:
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[GYMPerson playGame]: unrecognized selector sent to instance 0x101843700'
原因在于我们没有playGame方法的具体实现,虽然类中复写了resolveInstanceMethod方法,但是在方法里没有做任何操作。
下面将resolveInstanceMethod方法里面加点代码:
+ (BOOL)resolveInstanceMethod:(SEL)sel {
if (sel == @selector(playGame)) {
Method method = class_getInstanceMethod(self, @selector(sleep));
const char *types = method_getTypeEncoding(method);
IMP imp = class_getMethodImplementation(self, @selector(sleep));
bool isAdded = class_addMethod(self, sel, imp, types);
return isAdded;
}
return [super resolveInstanceMethod:sel];
}
然后再运行程序,便会得到:
GYMDemo[21368:7583751] -[GYMPerson sleep] 调用了
此时sleep方法调用了,程序没有崩溃,因为在resolveInstanceMethod方法里面,我们将sleep方法的IMP绑定给了playGame方法,所以执行playGame方法,就是执行sleep方法。
以上就是实例方法的动态决议,下面再看一下类方法的动态决议。
2.2 类方法动态决议
类方法动态决议方法如下:
/***********************************************************************
* _class_resolveClassMethod
* Call +resolveClassMethod, looking for a method to be added to class cls.
* cls should be a metaclass.
* Does not check if the method already exists.
**********************************************************************/
//cls: 元类, sel: 方法, inst: 类
static void _class_resolveClassMethod(Class cls, SEL sel, id inst)
{
assert(cls->isMetaClass());
/**
判断元类中是否实现了resolveClassMethod方法。
如果元类没有实现,找根元类,根元类没有,则找NSObject。
*/
if (! lookUpImpOrNil(cls, SEL_resolveClassMethod, inst,
NO/*initialize*/, YES/*cache*/, NO/*resolver*/))
{
// Resolver not implemented.
return;
}
// 创建消息,给类发送resolveClassMethod消息,此时本类的resolveClassMethod方法会被调用,如果没实现,那么调用父类的该方法。
BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
bool resolved = msg(_class_getNonMetaClass(cls, inst),
SEL_resolveClassMethod, sel);
// Cache the result (good or bad) so the resolver doesn't fire next time.
// +resolveClassMethod adds to self->ISA() a.k.a. cls
IMP imp = lookUpImpOrNil(cls, sel, inst,
NO/*initialize*/, YES/*cache*/, NO/*resolver*/);
if (resolved && PrintResolving) {
if (imp) {
_objc_inform("RESOLVE: method %c[%s %s] "
"dynamically resolved to %p",
cls->isMetaClass() ? '+' : '-',
cls->nameForLogging(), sel_getName(sel), imp);
}
else {
// Method resolver didn't add anything?
_objc_inform("RESOLVE: +[%s resolveClassMethod:%s] returned YES"
", but no new implementation of %c[%s %s] was found",
cls->nameForLogging(), sel_getName(sel),
cls->isMetaClass() ? '+' : '-',
cls->nameForLogging(), sel_getName(sel));
}
}
}
下面看一下NSObject类的resolveClassMethod方法实现:
+ (BOOL)resolveClassMethod:(SEL)sel {
return NO;
}
那么当我们实现的这个方法被调用了,都可以做什么呢?来看下面一段代码:
@interface GYMPerson : NSObject
+ (void)loveGirl;
+ (void)loveLife;
@end
@implementation GYMPerson
+ (void)loveLife {
NSLog(@"%s 调用了", __func__);
}
+ (BOOL)resolveClassMethod:(SEL)sel {
return [super resolveClassMethod:sel];
}
@end
上面有一个GYMPerson类,有两个类方法,其中loveGirl只定义,没有具体实现, loveLife既定义又实现,然后咱们开始调用loveGirl方法。
int main(int argc, const char * argv[]) {
@autoreleasepool {
[GYMPerson loveGirl];
}
return 0;
}
当调用loveGirl方法后,程序直接挂掉了,报错如下:
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '+[GYMPerson loveGirl]: unrecognized selector sent to class 0x100002750'
原因在于我们没有loveGirl方法的具体实现,虽然类中复写了resolveClassMethod方法,但是在方法里没有做任何操作。
下面将resolveClassMethod方法里面加点代码:
+ (BOOL)resolveClassMethod:(SEL)sel {
if (sel == @selector(loveGirl)) {
Method method = class_getClassMethod(objc_getMetaClass("GYMPerson"), @selector(loveLife));
const char *types = method_getTypeEncoding(method);
IMP imp = class_getMethodImplementation(objc_getMetaClass("GYMPerson"), @selector(loveLife));
bool isAdded = class_addMethod(objc_getMetaClass("GYMPerson"), sel, imp, types);
return isAdded;
}
return [super resolveClassMethod:sel];
}
然后再运行程序,便会得到:
GYMDemo[16988:9071636] +[GYMPerson loveLife] 调用了
此时loveLife方法调用了,程序没有崩溃,因为在resolveClassMethod方法里面,我们将loveLife方法的IMP绑定给了loveGirl方法,所以执行loveGirl方法,就是执行loveLife方法。
3. 消息转发
如果我们在代码里面没有实现对应的动态决议方法,那么程序在崩溃之前,苹果还给我们提供了一次转发的机会,分为快速转发和慢速转发,具体如下。
3.1 快速转发流程
所谓的快速转发流程就是指定给别人做。
实例方法快速转发如下:
- (id)forwardingTargetForSelector:(SEL)sel {
return nil;
}
这个方法返回一个对象类型,意思就是当前对象解决不了的话,那么可能别的类型的对象有,能解决。
比如有这么一个类GYMAthlete:
@interface GYMAthlete : NSObject
- (void)playGame;
@end
@implementation GYMAthlete
- (void)playGame {
NSLog(@"%s", __func__);
}运行程序[person playGame];后没有崩溃,而是调用了GYMAthlete类的playGame方法。
@end
GYMPerson类里面没有实现playGame方法,但是GYMAthlete类里面却有同名的方法,且实现了。
此时我们修改forwardingTargetForSelector方法:
- (id)forwardingTargetForSelector:(SEL)sel {
if (sel == @selector(playGame)) {
return [GYMAthlete alloc];
}
return nil;
}
运行程序[person playGame];后没有崩溃,而是调用了GYMAthlete类的playGame方法。
+ (id)forwardingTargetForSelector:(SEL)sel {
if (sel == @selector(loveGirl)) {
return [GYMAthlete class];
}
return nil;
}
运行程序[GYMPerson loveGirl];后没有崩溃,而是调用了GYMAthlete类的loveGirl方法。
GYMDemo[18597:9145003] +[GYMAthlete loveGirl]
以上就是快速转发流程,自己做不了的事情指定给别人做。
3.2 慢速转发流程
慢速转发流程则是我把方法抛出去,谁能解决谁就帮我解决了,没人解决也不会崩溃了,因为我已经扔出去了。
与快速转发不同的是,慢速转发不指定谁去处理,而是扔出去,那么仍出去的是什么呢?
方法签名
下面看一下签名的方法:
// 实例方法签名
- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel {
return nil;
}
// 类方法方法签名
+ (NSMethodSignature *)methodSignatureForSelector:(SEL)sel {
return nil;
}
只要一个签名方法还是没用呢,它还有一个好搭档,如下:
- (void)forwardInvocation:(NSInvocation *)anInvocation {
NSLog(@"%s", __func__);
}
+ (void)forwardInvocation:(NSInvocation *)invocation {
NSLog(@"%s", __func__);
}
只要一个签名方法还是没用呢,它还有一个好搭档,如下:
- (void)forwardInvocation:(NSInvocation *)anInvocation {
NSLog(@"%s", __func__);
}
+ (void)forwardInvocation:(NSInvocation *)invocation {
NSLog(@"%s", __func__);
}
可以通俗的说,先把方法签名,然后扔到invocation里面等待处理,有人处理则好,没人处理也不会崩溃。
下面试验一下:
int main(int argc, const char * argv[]) {
@autoreleasepool {
GYMPerson *person = [GYMPerson alloc];
[person playGame]; // 没有具体实现
[GYMPerson loveGirl]; // 没有具体实现
}
return 0;
}
// 运行playGame方法时,先进入到这里签名。
- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel {
if (sel == @selector(playGame)) {
NSMethodSignature *signature = [NSMethodSignature signatureWithObjCTypes:"v@:"];
return signature;
}
return nil;
}
// 运行loveGirl方法时,先进入到这里签名。
+ (NSMethodSignature *)methodSignatureForSelector:(SEL)sel {
if (sel == @selector(loveGirl)) {
NSMethodSignature *signature = [NSMethodSignature signatureWithObjCTypes:"v@:"];
return signature;
}
return nil;
}
// playGame签名后,将进入forwardInvocation这个方法等待处理。
- (void)forwardInvocation:(NSInvocation *)anInvocation {
SEL sel = [anInvocation selector];
if (sel == @selector(playGame)) {
// 这里分别判断了GYMAthlete和GYMDeveloper的实例对象能不能处理,谁能处理交给谁处理。
if ([[GYMAthlete alloc] respondsToSelector:sel]) {
[anInvocation invokeWithTarget:[GYMAthlete alloc]];
}else if ([[GYMDeveloper alloc] respondsToSelector:sel]) {
[anInvocation invokeWithTarget:[GYMDeveloper alloc]];
}
}
}
// loveGirl签名后,将进入forwardInvocation这个方法等待处理。
+ (void)forwardInvocation:(NSInvocation *)invocation {
SEL sel = [invocation selector];
if (sel == @selector(loveGirl)) {
// 这里分别判断了GYMAthlete和GYMDeveloper类能不能处理,谁能处理交给谁处理。
if ([[GYMAthlete class] respondsToSelector:sel]) {
[invocation invokeWithTarget:[GYMAthlete class]];
}else if ([[GYMDeveloper class] respondsToSelector:sel]) {
[invocation invokeWithTarget:[GYMDeveloper class]];
}
}
}
结果如下:
2020-10-30 22:27:53.729149+0800 GYMDemo[74259:10203752] -[GYMAthlete playGame]
2020-10-30 22:27:53.729812+0800 GYMDemo[74259:10203752] +[GYMAthlete loveGirl]
4. 总结
本篇文章主要讲了消息的转发机制,当消息查找失败时就会进入转发阶段,转发分为三个阶段:
第一阶段:
动态决议_class_resolveMethod
,指定实现某个方法。
第二阶段:
forwardingTargetForSelector
: 指定某个对象或者类去实现同名的方法。
第三阶段:
methodSignatureForSelector
forwardInvocation
组合阶段:将方法签名扔出去,然后再forwardInvocation
方法中,谁想处理就处理。
以上三个阶段是逐一顺序实现的,如果某个阶段实现了,那么后续阶段不再调用了。
以上内容出自https://blog.csdn.net/guoyongming925的博客,转载请注明来源。
原文作者:Daniel_Coder
原文地址:https://blog.csdn.net/guoyongming925/article/details/109319991