ios 消息转发机制

在上篇文章方法查找流程通过在类和父类的缓存以及方法列表中进行查找,如果直到查找到NSObject中都没有找到,然后会进行动态方法解析,我们先来看一下这一步做了什么?

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,继续看这个方法:

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);
        }
    }
}

在这个方法中,通过判断我们不难判断出一个是对象方法解析,一个是类方法解析,因为我们知道对象方法是存在类中,类方法是存在元类中,所以! cls->isMetaClass()这个条件满足肯定是对象方法解析了,我们先看一下对象方法解析。

  • 对象方法动态解析_class_resolveInstanceMethod:
static void _class_resolveInstanceMethod(Class cls, SEL sel, id inst)
{
    if (! lookUpImpOrNil(cls->ISA(), SEL_resolveInstanceMethod, cls, 
                         NO/*initialize*/, YES/*cache*/, NO/*resolver*/)) 
    {
        // Resolver not implemented.
        return;
    }

    // 系统给你一次机会 - 你要不要针对 sel 来操作一下下
    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));
        }
    }
}
  • 首先调用lookUpImpOrNil函数,传入元类cls->ISA(),这里传入元类的原因是下面要查找SEL_resolveInstanceMethod,也就是+ (BOOL)resolveInstanceMethod:(SEL)sel这个方法,此方法在NSObject中有实现,是一个类方法,类方法在元类中。
  • 往下是执行objc_msgSend,就是在cls中发送一个消息,是否实现+ (BOOL)resolveInstanceMethod:(SEL)sel方法
  • 如果实现则再次通过lookUpImpOrNil查找imp
  • imp如果找到,则输出动态解析对象方法成功的日志
  • 如果imp 没有找到,则输出虽然实现了+(BOOL)resolveInstanceMethod:(SEL)sel,并且返回了 YES,但并没有查找到imp的日志
    其实这里没有找到方法,也即是没有imp,我们可以在+(BOOL)resolveInstanceMethod:(SEL)sel这个方法中进行一步操作,也就是给我们要查找的方法一个已存在的imp,这样就可以避免crash,代码奉上:
+ (BOOL)resolveInstanceMethod:(SEL)sel{
    // 获取到需要动态解析的方法名
    if (sel == @selector(saySomething)) {
        // 获取到需要动态解析到的方法sayHello的IMP和Method
        IMP sayHIMP = class_getMethodImplementation(self, @selector(sayHello));
        Method sayHMethod = class_getInstanceMethod(self, @selector(sayHello));
        const char *sayHType = method_getTypeEncoding(sayHMethod);
        // 通过API添加方法
        return class_addMethod(self, sel, sayHIMP, sayHType);
    }
    
    return [super resolveInstanceMethod:sel];
}
  • 类方法动态解析_class_resolveClassMethod:
static void _class_resolveClassMethod(Class cls, SEL sel, id inst)
{
    assert(cls->isMetaClass());

    if (! lookUpImpOrNil(cls, SEL_resolveClassMethod, inst, 
                         NO/*initialize*/, YES/*cache*/, NO/*resolver*/)) 
    {
        // Resolver not implemented.
        return;
    }

    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));
        }
    }
}
  • 首先判断是否是元类,不是元类直接退出,因为类方法在元类中。
  • 下面的执行跟对象方法的动态解析一样,把SEL_resolveInstanceMethod换成了SEL_resolveClassMethod,容错处理代码:
+ (BOOL)resolveClassMethod:(SEL)sel{
     if (sel == @selector(sayLove)) {
         // 获取到元类中存储的类方法sayObjc
         IMP sayHIMP = class_getMethodImplementation(objc_getMetaClass("LGStudent"), @selector(sayObjc));
         Method sayHMethod = class_getClassMethod(objc_getMetaClass("LGStudent"), @selector(sayObjc));
         const char *sayHType = method_getTypeEncoding(sayHMethod);
        // 将类方法实现添加在元类之中
         return class_addMethod(objc_getMetaClass("LGStudent"), sel, sayHIMP, sayHType);
     }
     return [super resolveClassMethod:sel];
}

如果在没有动态方法解析处理的情况下,调用了程序中没有的方法saySomethingsayLove,那么这个程序肯定会crash,但是如果实现了消息转发的处理,那么就可以将方法的调用转交其他的对象来处理,避免crash,来看一下消息转发。

消息转发

在消息的查找流程中有这样一段代码:

 // Superclass method list.
            Method meth = getMethodNoSuper_nolock(curClass, sel);
            if (meth) {
                log_and_fill_cache(cls, meth->imp, sel, inst, curClass);
                imp = meth->imp;
                goto done;
            }

就是方法日志的打印,调用了log_and_fill_cache方法,我们看源码:

static void
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函数中:

spinlock_t objcMsgLogLock;

#if !SUPPORT_MESSAGE_LOGGING

void    instrumentObjcMessageSends(BOOL flag)
{
}

#else

bool objcMsgLogEnabled = false;
static int objcMsgLogFD = -1;
bool logMessageSend(bool isClassMethod,
                    const char *objectsClass,
                    const char *implementingClass,
                    SEL selector)
{
    char    buf[ 1024 ];

    // Create/open the log file
    if (objcMsgLogFD == (-1))
    {
        snprintf (buf, sizeof(buf), "/tmp/msgSends-%d", (int) getpid ());
        objcMsgLogFD = secure_open (buf, O_WRONLY | O_CREAT, geteuid());
        if (objcMsgLogFD < 0) {
            // no log file - disable logging
            objcMsgLogEnabled = false;
            objcMsgLogFD = -1;
            return true;
        }
    }

    // Make the log entry
    snprintf(buf, sizeof(buf), "%c %s %s %s\n",
            isClassMethod ? '+' : '-',
            objectsClass,
            implementingClass,
            sel_getName(selector));

    objcMsgLogLock.lock();
    write (objcMsgLogFD, buf, strlen(buf));
    objcMsgLogLock.unlock();

    // Tell caller to not cache the method
    return false;
}
void instrumentObjcMessageSends(BOOL flag)
{
    bool enable = flag;

    // Shortcut NOP
    if (objcMsgLogEnabled == enable)
        return;

    // If enabling, flush all method caches so we get some traces
    if (enable)
        _objc_flush_caches(Nil);

    // Sync our log file
    if (objcMsgLogFD != -1)
        fsync (objcMsgLogFD);

    objcMsgLogEnabled = enable;
}

在这里我们可以看到日志的打印取决于objcMsgLogEnabled,而instrumentObjcMessageSends函数是赋值objcMsgLogEnabled的,所以,我们可以在外面暴露打印日志,而日志存在于/tmp这个文件夹下:

extern void instrumentObjcMessageSends(BOOL flag);

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        LGStudent *student = [LGStudent alloc] ;

        // 动态方法决议
        // 对象方法
        // 类方法 -
        
        instrumentObjcMessageSends(true);
        [student saySomething];
        instrumentObjcMessageSends(false);

    }
    return 0;
}

前往文件夹搜索/tmp,可以看到msgSends-%d这样的一个文件,打开:

+ LGStudent NSObject resolveInstanceMethod:
+ LGStudent NSObject resolveInstanceMethod:

- LGStudent NSObject forwardingTargetForSelector:
- LGStudent NSObject forwardingTargetForSelector:
- LGStudent NSObject methodSignatureForSelector:
- LGStudent NSObject methodSignatureForSelector:
- LGStudent NSObject class

- LGStudent NSObject doesNotRecognizeSelector:
- LGStudent NSObject doesNotRecognizeSelector:
- LGStudent NSObject class

可以看出在动态方法解析之后调用了forwardingTargetForSelector

消息快速转发流程

搜索一下源码,发现forwardingTargetForSelectorNSObject中有调用:

+ (id)forwardingTargetForSelector:(SEL)sel {
    return nil;
}

这是不是什么也看不出来,来,我们看一下官方文档:
option+command

搜索官网.png

选择Show Quick Help
搜索官方文档.png

搜索forwardingTargetForSelector选择OC
官方文档介绍.png

我们只需要看Discussion中:(大家找到之后可以找翻译软件翻译,能看懂的受小弟膜拜)

  • 该方法的返回对象是执行sel的新对象,也就是自己处理不了会将消息转发给别人的对象进行相关方法的处理,但是不能返回self,否则会一直找不到
  • 该方法的效率较高,如果不实现或者nil,会走到forwardInvocation:方法进行处理
  • 底层会调用objc_msgSend(forwardingTarget, sel, ...)来实现消息的发送
  • 被转发消息的接受者参数和返回值等需要和原方法相同
    具体实现以下:
- (id)forwardingTargetForSelector:(SEL)aSelector{
    NSLog(@"%s -- %@",__func__,NSStringFromSelector(aSelector));
    if (aSelector == @selector(saySomething)) {
        return [LGTeacher alloc];
    }
    return [super forwardingTargetForSelector:aSelector];
}

表示saySomething方法的处理被转发到LGTeacher的相关类中实现。至此消息转发的快速流程结束,不crash.

消息慢速转发流程

如果上述方法为nil的时候,就会进入慢速转发流程,methodSignatureForSelector进入此函数

// Replaced by CF (returns an NSMethodSignature)
+ (NSMethodSignature *)instanceMethodSignatureForSelector:(SEL)sel {
   _objc_fatal("+[NSObject instanceMethodSignatureForSelector:] "
               "not available without CoreFoundation");
}

// Replaced by CF (returns an NSMethodSignature)
+ (NSMethodSignature *)methodSignatureForSelector:(SEL)sel {
   _objc_fatal("+[NSObject methodSignatureForSelector:] "
               "not available without CoreFoundation");
}

// Replaced by CF (returns an NSMethodSignature)
- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel {
   _objc_fatal("-[NSObject methodSignatureForSelector:] "
               "not available without CoreFoundation");
}

源码中也是什么都看不到,看官方文档(方法和快速转发流程一样样的)

慢速消息转发官方文档.png

这里可以看到执行methodSignatureForSelector还要创建一个NSInvocation对象,这个又是什么东东,官方文档往下翻会有一个:
NSInvocation入口.png

点击forwardInvocation进入:

介绍.png

有点长,慢慢看,

  • 实现methodSignatureForSelector还必须要实现forwardInvocation
- (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];
}

运行看打印:

2020-01-04 21:09:02.267016+0800 008-方法查找-消息转发[2475:201381] -[LGStudent methodSignatureForSelector:] -- saySomething
2020-01-04 21:09:02.267376+0800 008-方法查找-消息转发[2475:201381] -[LGStudent forwardInvocation:]
2020-01-04 21:09:02.267578+0800 008-方法查找-消息转发[2475:201381] -[LGTeacher saySomething]

先进入methodSignatureForSelector然后到forwardInvocation,最后查找到saySomething

消息无法处理

执行函数doesNotRecognizeSelector,

+ (void)doesNotRecognizeSelector:(SEL)sel {
    _objc_fatal("+[%s %s]: unrecognized selector sent to instance %p", 
                class_getName(self), sel_getName(sel), self);
}

报出错误信息

总结:

消息转发流程.png
  • 消息方法查找首先进行MethodTableLookup常规慢速查找,没有找到imp
  • 首先进入动态方法解析_class_resolveMethod/_class_resolveInstanceMethod,根据对象方法和类方法,进行不同的解析处理(当然,类方法也可以当做元类的对象方法进行解析)
  • 动态方法解析返回NO,则进入消息转发的快速阶段forwardingTargetForSelector,也就是自己处理不了会将消息转发给别人的对象进行相关方法的处理
  • 如果消息快速转发阶段未能处理,则进入到常规慢速转发流程methodSignatureForSelector,先针对aSelector返回方法签名NSMethodSignature,再去forwardInvocation方法中集中处理

你可能感兴趣的:(ios 消息转发机制)