消息转发流程

消息转发机制

消息的查找流程分为:快速查找和慢速查找
消息转发机制也分为:快速和慢速
先来一个转发流程图


消息转发流程

之前我们的消息查找流程中有这端代码

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

如果resolver为true,triedResolver为false,那么就会走_class_resolveMethod

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

首先判断当前类是否为元类,如果是元类就意味着为类方法,否则就是实例方法.

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

    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));
        }
    }
}
 BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
    bool resolved = msg(cls, SEL_resolveInstanceMethod, sel);

现在发送一个SEL_resolveInstanceMethod的消息,SEL_resolveInstanceMethod是个什么东西呢
来看下注释 * Call +resolveInstanceMethod, looking for a method to be added to class cls.他是一个+ resolveInstanceMethod方法,那么我本类中没有实现resolveInstanceMethod这个方法啊为啥他就不蹦呢,因为他的父类中实现了,我们搜一下发现NSObject中实现了

image.png

,那么我们再来看一下上边的判断条件

if (! lookUpImpOrNil(cls->ISA(), SEL_resolveInstanceMethod, cls, 
                         NO/*initialize*/, YES/*cache*/, NO/*resolver*/)) 
    {
        // Resolver not implemented.
        return;
    }

这里是做了一个容错处理,因为有可能你的类没有继承NSObject,那就意味着找不到该方法,就没有必要再去发送消息,然后根据上边我们讲的消息查找流程,去父类中找最后找到NSObject中,所以这里不会出现崩溃
调用了一个只声明没有实现的方法直接崩溃,而且崩溃前会先调用一下resolveInstanceMethod.
那么怎么去挽救一下呢.
来重写resolveInstanceMethod.之所以崩溃是因为没有找到imp,那么我们可以给他加一个imp

//动态方法决议
+(BOOL)resolveInstanceMethod:(SEL)sel{
    NSLog(@"来了老弟");
//如果注释掉下边`{}`中代码,发现这里会来两次,打印两次来了老弟
//
    if (sel == @selector(saySomething)){
        IMP sayHIMP = class_getMethodImplementation(self, @selector(sayHello));
        Method method = class_getInstanceMethod(self, @selector(sayHello));
        const char *sayType = method_getTypeEncoding(method);
        return  class_addMethod(self, sel, sayHIMP, sayType);
    }
    return [super resolveInstanceMethod:sel];
}

完美解决崩溃问题
如果这里没有做特殊处理,返回的是一个NO,那么会来到一个快速转发流程,

快速转发流程
  • 首先来看一下打印
    我们忽略了一个函数 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);
}

如果objcMsgLogEnabled为true则打印,点击objcMsgLogEnabled看下他的实现

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设置为true就可以看到打印信息了.
我们发现下边这个函数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;
}

我们只需要调用这个函数传入一个trueok了,比如

extern void instrumentObjcMessageSends(BOOL);

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        LGStudent *student = [[LGStudent alloc] init];
        instrumentObjcMessageSends(YES);
        [student saySomething];
        instrumentObjcMessageSends(NO);
    }
    return 0;
}

然后打开/private/tmp文件目录

如图

+ LGStudent NSObject resolveInstanceMethod:
+ LGStudent NSObject resolveInstanceMethod:
- LGStudent NSObject forwardingTargetForSelector:
- LGStudent NSObject forwardingTargetForSelector:
- LGStudent NSObject methodSignatureForSelector:
- LGStudent NSObject methodSignatureForSelector:
- LGStudent NSObject class
+ LGStudent NSObject resolveInstanceMethod:
+ LGStudent NSObject resolveInstanceMethod:
- LGStudent NSObject doesNotRecognizeSelector:
- LGStudent NSObject doesNotRecognizeSelector:
- LGStudent NSObject class

看到他调用了forwardingTargetForSelector
看一下文档

Discussion
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.)

大致意思就是如果自己没有实现可以返回一个新的实现了改方法的对象
那么如果动态方法决议没有处理,那么来到这个方法

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

我们这里返回一个teacher没让他来实现这个方法,那么如果他也没有实现呢,就会来到下一个消息转发流程
慢速转发流程

慢速转发
//根据selector生成一个NSMethodSignature方法签名并返回
//方法签名其实就是,方法的参数返回值类型的一些信息
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
  • 使用
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
   NSLog(@"%s -- %@",__func__,NSStringFromSelector(aSelector));
   if (aSelector == @selector(saySomething)) { // v @ :
       return [NSMethodSignature signatureWithObjCTypes:"v@:"];
   }
   return [super methodSignatureForSelector:aSelector];
}

方法签名TypeEncoding

那么只有签名没有实现是不行的

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

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

    }
    return 0;
}

@implementation LGStudent
// 慢速转发
- (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];
}
@end

如上,如果student调用一个没有实现的方法,那么回来到慢速转发流程,返回签名之后,我们把这个消息指派给实现了saySomething方法的teacher类,如果没有指派,那么直接崩溃.至此我们的消息转发流程走完

利用消息转发预防崩溃方案

上边讲了,runtime给出的三种补救方式

  1. 调用resolveInstanceMethod给个机会让类添加这个实现这个函数

  2. 调用forwardingTargetForSelector让别的对象去执行这个函数

  3. 调用forwardInvocation(函数执行器)灵活的将目标函数以其他形式执行。

如果都不中,调用doesNotRecognizeSelector抛出异常。选择哪一种合适呢?
1.resolveInstanceMethod需要在类上动态添加他本身不存在的方法,这些方法对于类本身来说是冗余的,不合适

  1. forwardInvocation 可以通过 NSInvocation 的形式将消息转发给多个对象,但是其开销大,需要创建 NSInvocation对象,并且forwardInvocation的函数经常被使用者调用,来做多层消息转发机制,不适合多次重写
  2. fowardIngTargetForSelector可以将消息转发给一个对象,开销小,并且被重写的概率较低,适合重写

这里要注意,如果原本类已经重写了forwardInvocation的话,就能对forwardingTargetForSelector进行重写了,这样会影响对象原本的消息转发

如何做?

  1. 首先动态创建一个类
  2. 给类动态添加对应的selector
  3. 将消息转发给这个类对象上

你可能感兴趣的:(消息转发流程)