iOS runtime 消息转发机制原理和实际用途

3,388 阅读3分钟

先来看一张图:

从全局来看,消息转发机制共分为3大步骤: 1.Method resolution 方法解析处理阶段 2.fast forwarding 快速转发阶段 3.Normal forwarding 常规转发阶段

抛出unrecognized selector 的报错,也就是需要从这3步里面.

第一步:我们称之为方法解析:

方法解析的含义就是:当我们的类发现我们调用的方法 unrecognize 的时候就会 调用第一个方法

// 类方法专用
+ (BOOL)resolveClassMethod:(SEL)sel
// 对象方法专用
+ (BOOL)resolveInstanceMethod:(SEL)sel

这个方法来询问当前对象是否能够处理sel,如果能那么return YES,在这个时候我们就可以通过 class_addMethod 来动态添加方法.让我们自己的类响应这个方法.这里为第一步的消息转发.如果我们的类的有这个方法也不会走到这里,很遗憾我们进入到第二步的转发:

+ (BOOL)resolveInstanceMethod:(SEL)sel
{
    NSString *method = NSStringFromSelector(sel);
    if ([@"playPiano" isEqualToString:method]) {
        /**
         添加方法
         
         @param self 调用该方法的对象
         @param sel 选择子
         @param IMP 新添加的方法,是c语言实现的
         @param 新添加的方法的类型,包含函数的返回值以及参数内容类型,eg:void xxx(NSString *name, int size),类型为:v@i
         */
        class_addMethod(self, sel, (IMP)playPiano, "v");
        return YES;
    }
    return NO;
}

第二步:称之为 "备援接受者"

意思就是:当前的类不能够实现这个sel,但是检查是否有备胎可以实现. "fuck 很痛苦!!!!!!!!!!"

- (id)forwardingTargetForSelector:(SEL)aSelector

这个方法就是消息转发的第二步:来找可以实现该方法的对象.

- (id)forwardingTargetForSelector:(SEL)aSelector
{
    NSString *seletorString = NSStringFromSelector(aSelector);
    if ([@"playPiano" isEqualToString:seletorString]) {
        Student *s = [[Student alloc] init];
        return s;
    }
    // 继续转发
    return [super forwardingTargetForSelector:aSelector];
}

我们的teacher 类不能实现 playPanio的方法 ,消息转发给我的学生来实现.直接将该消息转给student类. 但最后我们的程序崩溃了,说明比较悲哀,连备胎都没有. 那我们就进入最后一步啦,还是将消息转发给其他的类.这里不仅可以转给其他的类,而且可以调用其他的方法. 比如:我调用:[Teacher playPaino] 方法,进过最后的消息转发,我调用的 student类的 travel 方法. 但是很少有人会在这一步做处理,我们知道方法越靠后,需要的东西就越多,资源开销就比较大,所以在这种情况下,一般用来增加参数,或者改变选择子.

- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
- (void)forwardInvocation:(NSInvocation *)anInvocation

第三步: 首先获取当前的方法签名: Signature

- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
{
    NSString *method = NSStringFromSelector(aSelector);
    if ([@"playPiano" isEqualToString:method]) {
        
        NSMethodSignature *signature = [NSMethodSignature signatureWithObjCTypes:"v@:@"];
        return signature;
    }
    return nil;
}

然后通过:

  • (void)forwardInvocation:(NSInvocation *)anInvocation

进行消息转发:

- (void)forwardInvocation:(NSInvocation *)anInvocation
{
    SEL sel = @selector(travel:);
    NSMethodSignature *signature = [NSMethodSignature signatureWithObjCTypes:"v@:@"];
    anInvocation = [NSInvocation invocationWithMethodSignature:signature];
    [anInvocation setTarget:self];
    [anInvocation setSelector:@selector(travel:)];
    NSString *city = @"北京";
    // 消息的第一个参数是self,第二个参数是选择子,所以"北京"是第三个参数
    [anInvocation setArgument:&city atIndex:2];
    
    if ([self respondsToSelector:sel]) {
        [anInvocation invokeWithTarget:self];
        return;
    } else {
        Student *s = [[Student alloc] init];
        if ([s respondsToSelector:sel]) {
            [anInvocation invokeWithTarget:s];
            return;
        }
    }
    
    // 从继承树中查找
    [super forwardInvocation:anInvocation];
}

如果当前类实现了 travel 方法 那你调用 playPaino的方法就不会崩溃, 如果没实现,但 student 实现了 也不会崩溃.

崩溃的过程大致就是这三步,最后一步还是不能找到实现方法,那就抛出异常,dose not recognize Selector

注意:

类方法需要添加到元类里面,oc中所有的类本质上来说都是对象,对象的ISA指针指向本类,类的isa指针指向元类.元类的isa指针指向根源类,根源类的isa指针指向自己,这样的话就形成了一个闭环.