OC底层消息转发机制

2020-12-14  本文已影响0人  iOS发呆君

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

上一篇下一篇

猜你喜欢

热点阅读