美文网首页
OC底层消息转发机制

OC底层消息转发机制

作者: iOS发呆君 | 来源:发表于2020-12-14 15:38 被阅读0次

    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

    相关文章

      网友评论

          本文标题:OC底层消息转发机制

          本文链接:https://www.haomeiwen.com/subject/wevvgktx.html