8.iOS底层探索之消息转发机制

286 阅读11分钟

一、动态方法决议(动态方法解析)

上篇文章7.iOS底层之消息查找流程中提到,lookUpImpOrForward方法的慢速查找过程中,如果在本类和父类的缓存和方法列表中都没找到,就会调用一次动态方法解析

    // No implementation found. Try method resolver once.
    // 没有找到方法实现,调用一次方法动态解析
    
    ️//首先检查是否已经被标记为动态方法解析,如果没有才会进入动态方法解析
    if (resolver  &&  !triedResolver) {
        runtimeLock.unlockRead();
        /*
         实例方法解析:_class_resolveInstanceMethod
         类方法解析:_class_resolveClassMethod
         */
        _class_resolveMethod(cls, sel, inst);
        runtimeLock.read();
         // 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,下次就不会再进入动态方法解析
        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);
        }
    }
}

lookUpImpOrNil

IMP lookUpImpOrNil(Class cls, SEL sel, id inst, 
                   bool initialize, bool cache, bool resolver)
{
    IMP imp = lookUpImpOrForward(cls, sel, inst, initialize, cache, resolver);
    if (imp == _objc_msgForward_impcache) return nil;
    else return imp;
}

如果是实例方法,执行_class_resolveInstanceMethod;是类方法,执行_class_resolveClassMethod,然后判断lookUpImpOrNil,如果没找到,再执行_class_resolveInstanceMethod,类方法为什么会决议2次呢,我们下面再讨论。

1.1、实例方法决议 _class_resolveInstanceMethod cls是类
/***********************************************************************
* _class_resolveInstanceMethod
* Call +resolveInstanceMethod, looking for a method to be added to class cls.
**********************************************************************/
static void _class_resolveInstanceMethod(Class cls, SEL sel, id inst)
{
    //判断系统是否实现SEL_resolveInstanceMethod方法,即+(BOOL)resolveInstanceMethod:(SEL)sel
    if (! lookUpImpOrNil(cls->ISA(), SEL_resolveInstanceMethod, cls, 
                         NO/*initialize*/, YES/*cache*/, NO/*resolver*/)) 
    {
        // Resolver not implemented.
        // 没实现直接return,就不会给 cls 发送 resolveInstanceMethod 消息,就不会报找不到 resolveInstanceMethod
        return;
    }
    //给objc_msgSend多添加一个参数
    BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
    // 实现了resolveInstanceMethod,通过objc_msgSend调用一下resolveInstanceMethod方法,可以在 resolveInstanceMethod 进行自定义处理
    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 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));
        }
    }
}

如果要进行实例方法的动态解析的话,要在方法 + (BOOL)resolveInstanceMethod:(SEL)sel中进行处理,我们需要通过对未实现的方法指定一个已经实现的方法的IMP,并添加到类中

+ (BOOL)resolveInstanceMethod:(SEL)sel{
    // 获取到需要动态解析的方法名
    if (sel == @selector(saySomething)) {
        NSLog(@"说话了");
         // 获取到需要动态解析到的方法sayHello的IMP和Method
        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];
}
1.2、类方法决议 _class_resolveClassMethod cls是元类
/***********************************************************************
* _class_resolveClassMethod
* Call +resolveClassMethod, looking for a method to be added to class cls.
**********************************************************************/
static void _class_resolveClassMethod(Class cls, SEL sel, id inst)
{
    assert(cls->isMetaClass());
    //判断元类是否实现SEL_resolveInstanceMethod方法,即+(BOOL)resolveInstanceMethod:(SEL)sel,如果找不到直接return
    if (! lookUpImpOrNil(cls, SEL_resolveClassMethod, inst, 
                         NO/*initialize*/, YES/*cache*/, NO/*resolver*/)) 
    {
        // Resolver not implemented.
        return;
    }
    //给objc_msgSend多添加一个参数
    BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
    // _class_getNonMetaClass通过元类和对象来找到类
    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));
        }
    }
}

我们看看_class_getNonMetaClass

Class _class_getNonMetaClass(Class cls, id obj)
{
    mutex_locker_t lock(runtimeLock);
    cls = getNonMetaClass(cls, obj);
    assert(cls->isRealized());
    return cls;
}

接着进入 getNonMetaClass,这个方法的目的就是通过元类获取类

展开查看getNonMetaClass源码

static Class getNonMetaClass(Class metacls, id inst)
{
    static int total, named, secondary, sharedcache;
    realizeClass(metacls);
    total++;
    // 如果已经不是元类的,那就直接返回
    if (!metacls->isMetaClass()) return metacls;
    // metacls really is a metaclass
// where inst == inst->ISA() == metacls is possible
// 根元类的isa指向的是自己
if (metacls->ISA() == metacls) {
    Class cls = metacls->superclass;
    assert(cls->isRealized());
    assert(!cls->isMetaClass());
    assert(cls->ISA() == metacls);
    if (cls->ISA() == metacls) return cls;
}
// 如果实例不为空
if (inst) {
    Class cls = (Class)inst;
    realizeClass(cls);
    // cls 可能是一个子类,这里通过实例获取到类对象,
    // 然后通过一个 while 循环来遍历判断类对象的 isa 是否是元类
    // 如果是元类的话,就跳出循环;如果不是接着获取类对象的父类
    // cls may be a subclass - find the real class for metacls
    while (cls  &&  cls->ISA() != metacls) {
        cls = cls->superclass;
        realizeClass(cls);
    }
    // 说明已经找到了当前元类所匹配的类
    if (cls) {
        assert(!cls->isMetaClass());
        assert(cls->ISA() == metacls);
        return cls;
    }
#if DEBUG
    _objc_fatal("cls is not an instance of metacls");
#else
    // release build: be forgiving and fall through to slow lookups
#endif
}
// 尝试命名查询
{
    Class cls = getClass(metacls->mangledName());
    if (cls->ISA() == metacls) {
        named++;
        if (PrintInitializing) {
            _objc_inform("INITIALIZE: %d/%d (%g%%) "
                         "successful by-name metaclass lookups",
                         named, total, named*100.0/total);
        }
        realizeClass(cls);
        return cls;
    }
}
// 尝试 NXMapGet
{
    Class cls = (Class)NXMapGet(nonMetaClasses(), metacls);
    if (cls) {
        secondary++;
        if (PrintInitializing) {
            _objc_inform("INITIALIZE: %d/%d (%g%%) "
                         "successful secondary metaclass lookups",
                         secondary, total, secondary*100.0/total);
        }
        assert(cls->ISA() == metacls);            
        realizeClass(cls);
        return cls;
    }
}
// try any duplicates in the dyld shared cache
// 尝试从 dyld 动态共享缓存库中查询
{
    Class cls = nil;
    int count;
    Class *classes = copyPreoptimizedClasses(metacls->mangledName(),&count);
    if (classes) {
        for (int i = 0; i < count; i++) {
            if (classes[i]->ISA() == metacls) {
                cls = classes[i];
                break;
            }
        }
        free(classes);
    }
    if (cls) {
        sharedcache++;
        if (PrintInitializing) {
            _objc_inform("INITIALIZE: %d/%d (%g%%) "
                         "successful shared cache metaclass lookups",
                         sharedcache, total, sharedcache*100.0/total);
        }
        realizeClass(cls);
        return cls;
    }
}
_objc_fatal("no class for metaclass %p", (void*)metacls);
}

当我们要进行类方法的动态解析时,需要添加+ (BOOL)resolveClassMethod:(SEL)sel进行动态方法解析

+ (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];
}
1.3、类方法为什么解析2次?(类方法时 , 对元类进行了动态方法解析没找到 imp 时 , 还要再对类进行实例方法的解析 )
_class_resolveClassMethod(cls, sel, inst);
if (!lookUpImpOrNil(cls, sel, inst, 
            NO/*initialize*/, YES/*cache*/, NO/*resolver*/)) 
{
    // 为什么这里还要查找一次呢?
    _class_resolveInstanceMethod(cls, sel, inst);
}

先贴出isa经典走位图

  • 类方法存储在元类中 , 实例方法存储在类中, 都是存储在方法列表里, 并无 + / - 之分
  • 根据isa走位图 , 根元类的父类是NSObject, 也就是说在元类中存储的类方法等价于在类中存储的对象方法

二、消息转发

如果动态方法解析也没实现,就会执行消息转发

    // No implementation found, and method resolver didn't help. 
    // Use forwarding.
    //未能找到方法实现,且方法的动态解析也没,就会走到消息转发流程
    
    imp = (IMP)_objc_msgForward_impcache;
    cache_fill(cls, sel, imp, inst);
2.1、汇编探索

我们给_objc_msgForward_impcache下符号断点,并汇编跟踪,得出大概如下流程: _objc_msgForward_impcache->_objc_msgForward->___forwarding___

进入___forwarding___找到如下方法调用:forwardingTargetForSelector->methodSignatureForSelector->forwardInvocation->doesNotRecognizeSelector

或者可以查看报错之后的汇编

2.2、系统消息日志探索

消息查找流程的时候我们提到了一个方法log_and_fill_cache,是用来缓存方法的,他还有一个日志缓存的方法logMessageSend 如下:

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


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

我们找到objcMsgLogEnabled的赋值是在instrumentObjcMessageSends

// 控制打印开关
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;
}

我们可以暴露instrumentObjcMessageSends这个方法,来达到外部打日志的目的

#import "LGStudent.h"
extern void instrumentObjcMessageSends(BOOL flag);
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        LGStudent *student = [[LGStudent alloc] init];
        
        instrumentObjcMessageSends(true);
        [student saySomething];
        instrumentObjcMessageSends(false);
        
    }
    return 0;
}

运行工程,来到 /private/tmp 目录下 , 打开最新的一份 msgSends-pid 文件,打开如下:

+ 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

我们看到了熟悉的 resolveInstanceMethod,但是在这之后有 2 个之前我们没探索过的方法: forwardingTargetForSelectormethodSignatureForSelector。然后会有 doesNotRecognizeSelector 方法

2.3、快速转发流程 forwardingTargetForSelector

没有源码,不急,我们用Xcode查看官方文档

If an object implements (or inherits) this method, and returns a non-nil (and non-self) result, that returned object is used as the new receiver object and the message dispatch resumes to that new object. (Obviously if you return self from this method, the code would just fall into an infinite loop.)
如果一个对象实现或继承了该方法,然后返回一个非空(非 self)的结果,那么这个返回值会被当做新的消息接受者对象,消息会被转发到该对象身上。(如果你在这个方法里返回 self,那么显然就会发生一个死循环)。
If you implement this method in a non-root class, if your class has nothing to return for the given selector then you should return the result of invoking super’s implementation.
如果你在一个非基类中实现了该方法,并且这个类没有任何可以返回的内容,那么你需要返回父类的实现。也就是 return [super forwardingTargetForSelector:aSelector];。
This method gives an object a chance to redirect an unknown message sent to it before the much more expensive forwardInvocation: machinery takes over. This is useful when you simply want to redirect messages to another object and can be an order of magnitude faster than regular forwarding. It is not useful where the goal of the forwarding is to capture the NSInvocation, or manipulate the arguments or return value during the forwarding.
这个方法使对象有机会在更昂贵的 forwardInvocation: 机械接管之前重定向发送给它的未知消息。当你只想将消息重定向到另一个对象,并且比常规转发快一个数量级时,这个方法就很有用。在转发的目标是捕获 NSInvocation 或在转发过程中操纵参数或返回值的情况下,此功能就无用了。
  • 该方法的返回对象是执行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];
}
2.4、慢速转发流程 methodSignatureForSelector

官方的定义是 methodSignatureForSelector 返回一个 NSMethodSignature 方法签名对象, 这个方法签名里面其实就是封装了返回值类型,参数类型等信息,还必须实现forwardInvocation方法,forwardInvocation官方描述如下

To respond to methods that your object does not itself recognize, you must override methodSignatureForSelector: in addition to forwardInvocation:. The mechanism for forwarding messages uses information obtained from methodSignatureForSelector: to create the NSInvocation object to be forwarded. Your overriding method must provide an appropriate method signature for the given selector, either by pre formulating one or by asking another object for one.
要响应对象本身无法识别的方法,除了 forwardInvocation:外,还必须重写methodSignatureForSelector: 。 转发消息的机制使用从methodSignatureForSelector:获得的信息来创建要转发的 NSInvocation 对象。 你的重写方法必须为给定的选择器提供适当的方法签名,方法是预先制定一个公式,也可以要求另一个对象提供一个方法签名。
  • forwardInvocationmethodSignatureForSelector必须是同时存在的,底层会通过方法签名,生成一个NSInvocation,将其作为参数传递调用
  • 该方法可以自由指派多个对象来接收这个消息
  • 使用 anInvocation将消息发送到该对象。anInvocation将保存结果,运行时系统将提取结果并将其传递给原始发送者。
  • 该方法可以指定多个转发者 , 而且由于 NSInvocation 的封装 , 可以自由调配 target , 参数等等 , 自由度较高 , 但与此同时花费也将更高

我们实现如下:

- (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];
}

三、总结

  • 当方法查找流程结束后仍没有找到 IMP,就会进行动态方法解析,之后再进入快速的消息转发,最后慢速消息转发
  • 动态方法解析:调用 +resolveInstanceMethod+resolveClassMethod 尝试获取 IMP,由于根元类继承NSObject,实例方法和类方法最终都会执行[NSObject resolveInstanceMethod:]
  • -forwardingTargetForSelector: 尝试获取一个可以处理的对象
  • 慢速转发,调用 -methodSignatureForSelector: 获取到方法签名后,将消息封装为一个invocation 再调用 -forwardInvocation: 进行处理。
  • doesNotRecognizeSelector抛出异常 unrecognized selector sent to instance %p
  • forwardingTargetForSelector:forwardInvocation:区别:前者参数和返回值需要和原方法一致,后者没有这个限制
  • 可见,当一个方法没有实现时,runtime 给了3次机会让我们进行处理。

大神画的流程图