OC底层消息转发机制

1. 前言

上一篇文章(OC底层方法的本质、查找流程)主要说了方法的查找流程,可是方法最后找到了NSObject都没有对应的方法实现,就直接崩溃吗?固然不是的,苹果还给咱们提供一个消息转发的机制,下面我们来具体看一下。面试

做为一个开发者,有一个学习的氛围跟一个交流圈子特别重要,这是一个个人iOS交流群:812157648,无论你是小白仍是大牛欢迎入驻 ,分享BAT,阿里面试题、面试经验,讨论技术, 你们一块儿交流学习成长!markdown

2. 动态方法决议

在上一篇文章介绍的查找IMP的方法中,当找不到的时候,会执行一次_class_resolveMethod方法,具体实现以下(详细解释见注释):app

/***********************************************************************
* _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);
        }
    }
}
复制代码

上面的方法则是一个统一的调用入口,下面分别看一下。oop

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方法实现:spa

+ (BOOL)resolveInstanceMethod:(SEL)sel {
    return NO;
}
复制代码

那么当咱们实现的这个方法被调用了,均可以作什么呢?来看下面一段代码:.net

@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方法。code

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        GYMPerson *person = [GYMPerson alloc];
        [person playGame];
    }
    return 0;
}
复制代码

当调用playGame方法后,程序直接挂掉了,报错以下:orm

*** 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方法中,谁想处理就处理。

以上三个阶段是逐一顺序实现的,若是某个阶段实现了,那么后续阶段再也不调用了。

以上内容出自blog.csdn.net/guoyongming…的博客,转载请注明来源。

原文做者:Daniel_Coder

原文地址:blog.csdn.net/guoyongming…

相关文章
相关标签/搜索