iOS开发 Rumtime运行时之消息转发机制(三)

转载自:IOS开发工程师--周玉的博客 iOS 开发 深入浅出Runtime运行时之官方翻译--动态方法处理

iOS开发 Rumtime运行时之消息发送机制(一)
iOS开发 Runtime运行时之官方翻译--动态方法解析(二)
iOS开发 Rumtime运行时之消息转发机制(三)

消息转发机制概括

在Objective-C中,使用对象进行方法调用是一个消息发送的过程(Objective-C采用“动态绑定机制”,所以所要调用的方法直到运行期才能确定)。

点击这里查看 – 深入浅出Rumtime运行时之消息发送机制详解

方法在调用时,系统会查看这个对象能否接收这个消息(查看这个类有没有这个方法,或者有没有实现这个方法),如果不能并且只在不能的情况下,就会调用下面这几个方法,给你“补救”的机会,你可以先理解为几套防止程序crash的备选方案,我们就是利用这几个方案进行消息转发,注意一点,前一套方案实现后一套方法就不会执行。如果这几套方案你都没有做处理,那么程序就会报错crash。

OC的运行时在程序崩溃前提供了三次拯救程序的机会:

方案一:点击这里查看 – 深入浅出Runtime运行时之方法动态处理(Dynamic Method Resolution)详解

+ (BOOL)resolveInstanceMethod:(SEL)sel
+ (BOOL)resolveClassMethod:(SEL)sel

方案二:

- (id)forwardingTargetForSelector:(SEL)aSelector

方案三:

- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector;
- (void)forwardInvocation:(NSInvocation *)anInvocation;
iOS开发 Rumtime运行时之消息转发机制(三)_第1张图片
这里写图片描述

补救机会一:resolveInstanceMethod

iOS开发 Rumtime运行时之消息转发机制(三)_第2张图片
这里写图片描述
iOS开发 Rumtime运行时之消息转发机制(三)_第3张图片
这里写图片描述

Person.h

#import 
#include 

@interface Persion : NSObject

- (void)run;
@end

Person.m

#import "Persion.h"
#include 
#import "Animal.h"

#pragma mark - 方案一 Method Resolution
// 运行时的动态方法处理在动态运行时添加一个dynamicMethodIMP方法去实现run方法
void dynamicMethodIMP(id self, SEL _cmd) {
    NSLog(@" >> dynamicMethodIMP called---经过动态方法处理,run方法能正常执行了: 跑步更健康");
}

@implementation Persion

// 注释掉run方法的实现 不用动态方法处理和消息转发机制处理会崩溃
//- (void)run {
//    NSLog(@"跑步更健康");
//}

/*
 关于生成签名的类型"v@:"解释一下。每一个方法会默认隐藏两个参数,self、_cmd,self代表方法调用者,_cmd代表这个方法的SEL,签名类型就是用来描述这个方法的返回值、参数的,v代表返回值为void,@表示self,:表示_cmd。
 */
#pragma mark - 方案一 Method Resolution
// 对象方法,给该对象再一次实现run方法的机会--name未实现的方法
+(BOOL)resolveInstanceMethod:(SEL)name {
    NSLog(@" >> Instance resolving (实例对象动态处理未实现的):%@方法", NSStringFromSelector(name));

    // 判断是否是指定的未实现的run方法
    if (name == @selector(run)) {
        // 屏蔽掉动态添加方法后会报错
        class_addMethod([self class], name, (IMP)dynamicMethodIMP, "v@:");
        return YES;
    }
    return [super resolveInstanceMethod:name];
}

+(BOOL)resolveClassMethod:(SEL)name {
    NSLog(@" >> Class resolving %@", NSStringFromSelector(name));
    return [super resolveClassMethod:name];
}

补救机会二:第一次转发 forwardingTargetForSelector

iOS开发 Rumtime运行时之消息转发机制(三)_第4张图片
这里写图片描述
iOS开发 Rumtime运行时之消息转发机制(三)_第5张图片
这里写图片描述
iOS开发 Rumtime运行时之消息转发机制(三)_第6张图片
这里写图片描述
#pragma mark - 方案二 First Forwarding 第一次转发给其他对象实现
- (id)forwardingTargetForSelector:(SEL)aSelector {

    NSLog(@"方案二 First Forwarding 第一次转发给其他对象实现");

    NSString *selStr = NSStringFromSelector(aSelector);

    if ([selStr isEqualToString:@"run"]) {
        // 这里返回Animal类对象,让Animal去处理run消息
        return [[Animal alloc] init];
    }

    return [super forwardingTargetForSelector:aSelector];
}

补救机会三:第二次转发 methodSignatureForSelector

iOS开发 Rumtime运行时之消息转发机制(三)_第7张图片
这里写图片描述
iOS开发 Rumtime运行时之消息转发机制(三)_第8张图片
这里写图片描述
iOS开发 Rumtime运行时之消息转发机制(三)_第9张图片
这里写图片描述
/*
 methodSignatureForSelector用来生成方法签名,
 这个签名就是给forwardInvocation中的参数NSInvocation调用的。
 */
#pragma mark - 方案三 NSMethodSignature 方法签名 第二次转发给其他对象实现
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {

    NSLog(@"方案三 NSMethodSignature 方法签名 第二次转发给其他对象实现");

    NSString *sel = NSStringFromSelector(aSelector);
    if ([sel isEqualToString:@"run"]) {
        return [NSMethodSignature signatureWithObjCTypes:"v@:"];
    } else {
        return [super methodSignatureForSelector:aSelector];
    }
}

- (void)forwardInvocation:(NSInvocation *)anInvocation {
    SEL name = [anInvocation selector];
    NSLog(@" >> forwardInvocation for selector (方法签名):%@", NSStringFromSelector(name));
    Animal *dog = [[Animal alloc] init];
    if ([dog respondsToSelector:name]) {
        [anInvocation invokeWithTarget:dog];
    }
    else {
        [super forwardInvocation:anInvocation];
    }
}

总结

从上面的示例演示可以看出,动态方法处理是先于消息转发的。
如果向一个 Objective C 对象对象发送它无法处理的消息(selector),那么编译器会按照如下次序进 行处理:

1,首先看是否为该 selector 供了动态方法决议机制,如果 供了则转到 2;如果没有 供则转到 3;

2,如果动态方法决议真正为该selector 供了实现,那么就调用该实现,完成消息发送流程,消息转发 就不会进行了;如果没有 供,则转到 3;

3,其次看是否为该selector 供了消息转发机制,如果 供了消息了则进行消息转发,此时,无论消息 转发是怎样实现的,程序均不会crash。(因为消息调用的控制权完全交给消息转发机制处理,即使消息转发并没有做任何事情,运行也不会有错误,编译器更不会有错误示。);如果没 供消息转发机制, 则转到 4;

4,运行报错:无法识别的 selector,程序 crash;

参考: iOS消息转发机制

你可能感兴趣的:(iOS开发 Rumtime运行时之消息转发机制(三))