消息的转发机制

411 阅读3分钟

动态方法决议

一:源码分析

方法查找未找到时候,会来到方法决议。

    if (resolver  &&  !triedResolver) {
        runtimeLock.unlock();
        _class_resolveMethod(cls, sel, inst);
        runtimeLock.lock();
        // Don't cache the result; we don't hold the lock so it may have 
        // changed already. Re-do the search from scratch instead.
        triedResolver = YES;
        goto retry;
    }

进入_class_resolveMethod(cls, sel, inst);后,

**********************************************************************/
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 (!lookUpImpOrNil(cls, sel, inst, 
                            NO/*initialize*/, YES/*cache*/, NO/*resolver*/)) 
        {
            // 对象方法 决议
            _class_resolveInstanceMethod(cls, sel, inst);
        }
    }
}


/***********************************************************************

对象方法的决议进入_class_resolveInstanceMethod,类方法的方法决议进入到_class_resolveClassMethod(cls, sel, inst);

但是,如果在类方法决议时未找到有处理的 _class_resolveClassMethod 时,会进入到对象方法方法决议中,源码中下图:

二.使用实例

1.类方法 在.main 中调用 Student 类方法sayLove,Love在Student继承的LGPerson中声明,但是未实现。

// 类方法 如果找不到 - 动态方法决议
// 类 - 元类 - NSObject
// 1: resolveClassMethod 你是否处理 - 只要注意元类
// 2: resolveClassMethod 没有处理 - resolveInstanceMethod

+ (BOOL)resolveClassMethod:(SEL)sel{
    
    NSLog(@"来了类方法:%s - %@",__func__,NSStringFromSelector(sel));

     if (sel == @selector(sayLove)) {
         NSLog(@"说- 说你你爱我");
         IMP sayHIMP = class_getMethodImplementation(self, @selector(sayObjc));
         Method sayHMethod = class_getClassMethod(self, @selector(sayObjc));
         const char *sayHType = method_getTypeEncoding(sayHMethod);
         // 类方法在元类 objc_getMetaClass("LGStudent")
         return class_addMethod(self, sel, sayHIMP, sayHType);
     }
     return [super resolveClassMethod:sel];
}

2.对象方法 在.main 中创建Student 的对象 调用 对象方法saySomething,saySomething在Student继承的LGPerson中声明,但是未实现。

+ (BOOL)resolveInstanceMethod:(SEL)sel{
    
    NSLog(@"来了老弟:%s - %@",__func__,NSStringFromSelector(sel));

    if (sel == @selector(saySomething)) {
        NSLog(@"说话了");
        IMP sayHIMP = class_getMethodImplementation(self, @selector(sayHello));
        Method sayHMethod = class_getInstanceMethod(self, @selector(sayHello));
        const char *sayHType = method_getTypeEncoding(sayHMethod);
        return class_addMethod(self, sel, sayHIMP, sayHType);
    }
    
    return [super resolveInstanceMethod:sel];
}

至此就不会出现方法崩溃,崩溃打印由于方法未找到: unrecognized selector sent to instance 。

消息转发机制

方法的打印

方法查找源码中有这样一段代码

{
        Method meth = getMethodNoSuper_nolock(cls, sel);
        if (meth) {
            log_and_fill_cache(cls, meth->imp, sel, inst, cls);
            imp = meth->imp;
            goto done;
        }
    }

进入到log_and_fill_cache中,

log_and_fill_cache(Class cls, IMP imp, SEL sel, id receiver, Class implementer)
{
#if SUPPORT_MESSAGE_LOGGING
    if (objcMsgLogEnabled) {
        bool cacheIt = logMessageSend(implementer->isMetaClass(), 
                                      cls->nameForLogging(),
                                      implementer->nameForLogging(), 
                                      sel);
        if (!cacheIt) return;
    }
#endif
    cache_fill (cls, sel, imp, receiver);
}

在进入到logMessageSend中:

查看可以打印消息的赋值的地方: 取决于flag值,

用instrumentObjc方法打印崩溃的信息,

可以发现方法崩溃前,调用的一些其他的方法。

快速转发

Cooci_消息转发机制.png

1.forwardingTargetForSelector

forwardingTargetForSelector无法调入源码中,通过苹果开发文档中给出解释。

方法在没有动态决议处理后,会进入消息转发流程(forwardingTargetForSelector)。

2.测试实例

在.main 中创建Student 的对象 调用 对象方法saySomething,saySomething在Student继承的LGPerson中都未声明和实现,但是在继承NSObject的LGTeache中实现。

// 别人可能有?
// 快速流程 - 交给一个对象来处理

- (id)forwardingTargetForSelector:(SEL)aSelector{
    NSLog(@"%s -- %@",__func__,NSStringFromSelector(aSelector));
    if (aSelector == @selector(saySomething)) {
        return [LGTeacher alloc];
    }
    return [super forwardingTargetForSelector:aSelector];
}

慢速转发

1.methodSignatureForSelector

如果快速转发消息中未作处理,则会来到慢速转发的流程中。 methodSignatureForSelector同样源码中无法探寻,可以通过苹果开发文档查看如下:

2.测试实例

在.main 中创建Student 的对象 调用 对象方法saySomething,saySomething在Student继承的LGPerson中都未声明和实现,但是在继承NSObject的LGTeache中实现

// 慢速转发 -- 漂流瓶
// unre - 不崩溃
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
    NSLog(@"%s -- %@",__func__,NSStringFromSelector(aSelector));
    if (aSelector == @selector(saySomething)) { // v @ :
        return [NSMethodSignature signatureWithObjCTypes:"v@:"];
    }
    return [super methodSignatureForSelector:aSelector];
}

//
- (void)forwardInvocation:(NSInvocation *)anInvocation{
    NSLog(@"%s ",__func__);
    
    // 事情 - 事务 - 秘书 - 失效
    // 系统本质
//   SEL aSelector = [anInvocation selector];
//
//   if ([[LGTeacher alloc] respondsToSelector:aSelector])
//       [anInvocation invokeWithTarget:[LGTeacher alloc]];
//   else
//       [super forwardInvocation:anInvocation];
}