iOS Objective-C 消息的转发

  • iOS Objective -C alloc 调用流程

  • iOS Object-C init&new

  • iOS OC 对象的内存对齐原则

  • iOS Objective-C isa

  • iOS Objective-C isa 走位分析

  • iOS OC 类原理

  • iOS OC 方法的本质

  • iOS Objective-C 消息的查找

  • iOS Objective-C 消息的转发

  • iOS 应用加载dyld

  • Mach-O探索

  • iOS开发中『库』的区别与应用

  • iOS 应用的加载objc

  • iOS Objective-C 分类的加载

  • iOS Objective-C 类扩展

  • iOS Objective-C 关联对象

  • iOS Objective-C KVC 的常见用法

  • iOS Objective-C KVC 详解

  • iOS Objective-C KVO 常见用法

  • iOS Objective-C KVO 详解

  • iOS多线程 基础

iOS Objective-C 消息的转发

1.动态方法决议(解析)

在上一篇消息查找的文章中我们在消息查找中没有找到的消息就会进入动态方法决议代码中。为了连贯性,本篇中会重新且详细的讲解一下动态方法决议。

1.1 resolveMethod_locked

/***********************************************************************
* resolveMethod_locked
* Call +resolveClassMethod or +resolveInstanceMethod.
*
* Called with the runtimeLock held to avoid pressure in the caller
* Tail calls into lookUpImpOrForward, also to avoid pressure in the callerb
**********************************************************************/
static NEVER_INLINE IMP
resolveMethod_locked(id inst, SEL sel, Class cls, int behavior)
{
    runtimeLock.assertLocked();
    ASSERT(cls->isRealized());

    runtimeLock.unlock();

    if (! cls->isMetaClass()) {
        // try [cls resolveInstanceMethod:sel]
        resolveInstanceMethod(inst, sel, cls);
    } 
    else {
        // try [nonMetaClass resolveClassMethod:sel]
        // and [cls resolveInstanceMethod:sel]
        resolveClassMethod(inst, sel, cls);
        if (!lookUpImpOrNil(inst, sel, cls)) {
            resolveInstanceMethod(inst, sel, cls);
        }
    }

    // chances are that calling the resolver have populated the cache
    // so attempt using it
    return lookUpImpOrForward(inst, sel, cls, behavior | LOOKUP_CACHE);
}

resolveMethod_locked主要作用是判断类是否是元类

  • 如果不是则进入resolveInstanceMethod继续处理
  • 如果是则进入resolveClassMethod继续处理,并且通过lookUpImpOrNil判断非空,最后也会调用resolveInstanceMethod进行对象方法的动态决议,因为根据isa走位图,万物皆对象,最终都会继承自NSObject,最后会找到NSObject的对象方法中。

1.2 resolveInstanceMethod(对象方法动态决议)

/***********************************************************************
* resolveInstanceMethod
* Call +resolveInstanceMethod, looking for a method to be added to class cls.
* cls may be a metaclass or a non-meta class.
* Does not check if the method already exists.
**********************************************************************/
static void resolveInstanceMethod(id inst, SEL sel, Class cls)
{
    runtimeLock.assertUnlocked();
    ASSERT(cls->isRealized());
    SEL resolve_sel = @selector(resolveInstanceMethod:);

    if (!lookUpImpOrNil(cls, resolve_sel, cls->ISA())) {
        // Resolver not implemented.
        return;
    }

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

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

该函数实质是做了一次方法的决议操作

  1. 初始化一个selresolveInstanceMethod
  2. 然后查找该sel,找到后则继续处理(找到说明实现了该方法),找不到就直接返回
  3. 通过objc_msgSend发送消息,这里发送的是resolveInstanceMethod消息,如果返回YES则说明该方法被实现,否则未实现。
  4. 如果实现并且决议处做了转发,说明该sel指向了新的imp,并通过下面的打印来说明新IMP被动态实现,或者没找到。

举个例子:

声明一个saySomething的对象方法,但是没有实现,直接调用肯定会报方法找不到的错误,那么上述流程要怎样处理才能不报错呢?

实现代码如下:

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

当我们调用saySomething时,因为没有实现所以找不到该方法,当我们实现了resolveInstanceMethod后,并在其内部将saySomethingimp指定为我们已经实现了的sayHello方法,就不会引起崩溃,最终就会调用sayHello,这就是runtime给开发者留下的对于对象方法的一种容错处理。

1.3 resolveClassMethod(类方法动态决议)

/***********************************************************************
* resolveClassMethod
* Call +resolveClassMethod, looking for a method to be added to class cls.
* cls should be a metaclass.
* Does not check if the method already exists.
**********************************************************************/
static void resolveClassMethod(id inst, SEL sel, Class cls)
{
    runtimeLock.assertUnlocked();
    ASSERT(cls->isRealized());
    ASSERT(cls->isMetaClass());

    if (!lookUpImpOrNil(inst, @selector(resolveClassMethod:), cls)) {
        // Resolver not implemented.
        return;
    }

    Class nonmeta;
    {
        mutex_locker_t lock(runtimeLock);
        nonmeta = getMaybeUnrealizedNonMetaClass(cls, inst);
        // +initialize path should have realized nonmeta already
        if (!nonmeta->isRealized()) {
            _objc_fatal("nonmeta class %s (%p) unexpectedly not realized",
                        nonmeta->nameForLogging(), nonmeta);
        }
    }
    BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
    bool resolved = msg(nonmeta, @selector(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(inst, sel, cls);

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

该函数跟resolveInstanceMethod差不多,唯一的区别就是发消息的时候是向元类发送消息。其余的就不在赘述了。

举个例子:

跟对象方法的例子一样首先声明一个sayLove的类方法,然后没有实现。调用后肯定还是会崩溃,这里我们在resolveClassMethod方法中对齐进行处理。

实现代码如下:

+ (BOOL)resolveClassMethod:(SEL)sel{

    if (sel == @selector(sayLove)) {
        // 类方法在元类 objc_getMetaClass("LGStudent")
        NSLog(@"说- love");
        IMP sayOIMP = class_getMethodImplementation(objc_getMetaClass("LGStudent"), @selector(sayObjc));
        Method sayOMethod = class_getClassMethod(objc_getMetaClass("LGStudent"), @selector(sayObjc));
        const char *sayOType = method_getTypeEncoding(sayOMethod);
        return class_addMethod(objc_getMetaClass("LGStudent"), sel, sayOIMP, sayOType);
    }
    return [super resolveClassMethod:sel];
}

实现原理跟对象方法的实现也基本差不多当我们调用sayLove时,因为没有实现所以找不到该方法,当我们实现了resolveClassMethod后,并在其内部将sayLoveimp指定为我们已经实现了的sayObjc方法,就不会引起崩溃,最终就会调用sayObjc,这就是runtime给开发者留下的对于类方法的一种容错处理。这里有一点需要特别注意,就是类方法是存储在原类中的,无论使我们获取sayObjc时还是添加新的方法时都应该选择元类进行处理,否则就会找不到方法,从而触发resolveInstanceMethod对象方法的动态决议,如果还是找不到就会崩溃。如果在NSObject中,或者NSObject的分类中实现了resolveInstanceMethod并且使用同样的放处理sayLove,这时候同样可以解决由sayLove没有实现而引起的崩溃。实现代码如下:(NSObject分类中实现)

+ (BOOL)resolveInstanceMethod:(SEL)sel {
    if (sel == @selector(sayLove)) {
        NSLog(@"说话了");
        IMP sayHIMP = class_getMethodImplementation(self, @selector(sayEasy));
        Method sayHMethod = class_getInstanceMethod(self, @selector(sayEasy));
        const char *sayHType = method_getTypeEncoding(sayHMethod);
        return class_addMethod(self, sel, sayHIMP, sayHType);
    }
    
    return NO;
}

为什么可以这样:
主要原因是resolveMethod_locked中这两句代码决定的。上个isa走位图就会更加清晰。

// try [nonMetaClass resolveClassMethod:sel]
// and [cls resolveInstanceMethod:sel]
resolveClassMethod(inst, sel, cls);
if (!lookUpImpOrNil(inst, sel, cls)) {
    resolveInstanceMethod(inst, sel, cls);
}
isa流程图

由这个流程图我们可以知道,元类最终继承自根元类,根元类又继承自NSObject,我们的方法(消息)在原类中也是以对象方法的形式存在的,当调用lookUpImpOrNil时会递归查找父类的方法列表,我们无法操作元类以及根元类,因为它们是系统生成的,但是我们可以借助NSObject Category的方式来实现方法的动态决议。如果类实现了方法的动态决议就不会到这里,如果没实现才会到NSObject的方法动态决议。

2. 消息转发

2.1 _objc_msgForward_impcache

如果所有地方均没有实现方法的动态决议,那么我们的底层还会有什么处理呢?

const IMP forward_imp = (IMP)_objc_msgForward_impcache;

lookUpImpOrForward方法的一开始我们就初始化了如上代码所示的imp。当找不到方法且没有实现动态决议的相关处理,最后会将此sel_objc_msgForward_impcache进行配对,进入消息的转发流程,如下图。

_objc_msgForward_impcache

我们搜索_objc_msgForward_impcache最终又来到objc-msg-arm64.s文件处。代码如下:

STATIC_ENTRY __objc_msgForward_impcache

// No stret specialization.
b   __objc_msgForward

END_ENTRY __objc_msgForward_impcache

2.2 _objc_msgForward

通过源码我们可以看出__objc_msgForward_impcache内部实际是调用了_objc_msgForward,紧跟其后的源码就是__objc_msgForward,下面我们继续探索

ENTRY __objc_msgForward

adrp    x17, __objc_forward_handler@PAGE
ldr p17, [x17, __objc_forward_handler@PAGEOFF]
TailCallFunctionPointer x17
    
END_ENTRY __objc_msgForward

2.3 通过打印日志寻找流程

看了__objc_msgForward的源码并没有什么像objc_msgSend那样的有用信息,这里我们并不能发现什么,一时间仿佛线索断裂,苹果爸爸只是开源到如此地步,那么我们该如何研究消息转发的详细流程呢?回想以前的的步骤找到imp后会继续进行缓存的填充和日志的打印,在我们的开发过程中往往都会通过日志的打印来发现和解决问题,那么我们不妨看看日志都打印了什么。


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



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

通过上面的两个函数我们可以看到,在objcMsgLogEnabledtrue的时候日志会输出到/tmp/msgSends-xxx的目录下。那么该如何让objcMsgLogEnabledtrue呢,我们先不妨搜索一下,搜完后我们发现改变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;
}

那么我们就来试一试,首先要新建一个MacOS工程,然后extern一下,否则不能调用。调用完毕后我们来到/private/tmp目录下

日志的存储路径

日志结果

首先我们就看到了我们熟悉的resolveInstanceMethod,紧接着就是forwardingTargetForSelectormethodSignatureForSelector这两个方法我们就没见过了。然后就是doesNotRecognizeSelector,这个方法是打印日志的方法。我们来到objc4-779.1的源码中搜索这个几个方法,实现代码如下:


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

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

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

// Replaced by CF (throws an NSException)
+ (void)doesNotRecognizeSelector:(SEL)sel {
    _objc_fatal("+[%s %s]: unrecognized selector sent to instance %p", 
                class_getName(self), sel_getName(sel), self);
}

// Replaced by CF (throws an NSException)
- (void)doesNotRecognizeSelector:(SEL)sel {
    _objc_fatal("-[%s %s]: unrecognized selector sent to instance %p", 
                object_getClassName(self), sel_getName(sel), self);
}

这时候我们发现了unrecognized selector sent to instance这就是我们常见的崩溃错误的打印实现了。在这个打印完毕后我们在刚才查看日志的工程中的控制台还看到了如下的日志:

控制台日志

在控制台日志中我们看到了CoreFoundation框架中的___forwarding___的调用,但是我们知道CoreFoundation并没有开源很多,那么我们先看看官方文档,先查看一下forwardingTargetForSelectormethodSignatureForSelector

2.4 forwardingTargetForSelector(快速转发流程)

forwardingTargetForSelector

根据文档的释义,此方法是返回一个能够定位到未找到消息imp的对象(object),也就是说,这个对象没有实现该方法,那么就去找另一个对象。

举个例子:

我们在刚才打印日志的工程中在实现一个LGteacher的类,再其内部实现saySomething方法,然后在LGStudent中添加如下代码:

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

其实就是在forwardingTargetForSelector中实现了狸猫换太子的操作,切实应用了苹果官方文档的解释,返回了一个实现了该方法对象。打印结果如下:

打印结果

根据打印结果我们可以知道LGStudent实例对象发送的saySomething消息最后由LGteacher响应。关于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.)

译:如果一个对象实现(或继承)这个方法,并返回一个非nil(和非self)结果,那么返回的对象将用作新的接收者对象,消息分派将继续到这个新对象。(显然,如果从这个方法返回self,代码将陷入无限循环。) 实际你传self也不会死循环,在CoreFoundation___forwarding___:方法中我们可以看到在调用forwardingTargetForSelector后会调用class_respondsToSelector方法判断你返回的这个对象是否能够响应该这个sel,如果不可以则会继续走消息转发流程。所以个人觉得苹果这个文档就是为了告诉你别这么写,并不会真的循环引用。

image.png

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.

译:如果你在一个非根类中实现这个方法,并且你的类对于给定的选择器没有返回任何东西,那么你应该返回父类的实现的结果。

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,或者在转发过程中操纵参数或返回值,那么它就没有用了。

小结:

  1. forwardingTargetForSelector是一个更快的转发消息的流程,它能直接让其他可以响应的对象来响应未知消息。
  2. forwardingTargetForSelector不能反回self不然就会陷入死循环。(文档是这么写的,实际不是)
  3. 在非根类中实现该方法对于给定的选择器没有实现任何东西,则需要返回父类的实现也结果。
  4. forwardingTargetForSelector适用于将消息转发给其他可以响应的该消息的对象,其主要的意思就是返回值和参数必须都一样,否则还要进行其他流程。

2.5 methodSignatureForSelector(慢速转发流程)

我们还是先看看methodSignatureForSelector的官方文档

methodSignatureForSelector

这里的释义是methodSignatureForSelector返回一个NSMethodSignature类型的方法签名对象,该对象包含由给定选择器标识的方法的描述。这里只是个方法签名,对参数和返回值没有要求,这就是在forwardingTargetForSelector小结里面说的其他流程。

Discussion(讨论)


This method is used in the implementation of protocols. This method is also used in situations where an NSInvocation object must be created, such as during message forwarding. If your object maintains a delegate or is capable of handling messages that it does not directly implement, you should override this method to return an appropriate method signature.

译:该方法用于协议的实现。同时这个方法也用于必须创建NSInvocation对象的情况,比如在消息转发期间。如果您的对象维护一个委托或能够处理它没有直接实现的消息,您应该重写此方法以返回适当的方法签名。

在文档的末尾处我们还看到有一个叫forwardInvocation的方法,我们点进去看看

See Also
forwardInvocation

根据我文档的定义:在子重写以将消息转发给其他对象。

Discussion(讨论)


When an object is sent a message for which it has no corresponding method, the runtime system gives the receiver an opportunity to delegate the message to another receiver. It delegates the message by creating an NSInvocation object representing the message and sending the receiver a forwardInvocation: message containing this NSInvocation object as the argument. The receiver’s forwardInvocation: method can then choose to forward the message to another object. (If that object can’t respond to the message either, it too will be given a chance to forward it.)

译:当向对象发送没有对应方法的消息时,运行时系统给接收方一个机会将消息委托给另一个接收方。它通过创建一个表示消息的NSInvocation对象并向接收者发送一个包含这个NSInvocation对象作为参数的forwardInvocation:消息来委托消息。然后,接收方的forwardInvocation:方法可以选择将消息转发到另一个对象。(如果该对象也不能响应消息,那么它也将获得一个转发消息的机会。)

The forwardInvocation: message thus allows an object to establish relationships with other objects that will, for certain messages, act on its behalf. The forwarding object is, in a sense, able to “inherit” some of the characteristics of the object it forwards the message to.

译:因此,forwardInvocation: message允许对象与其他对象建立关系,对于某些消息,这些对象将代表它行事。在某种意义上,转发对象能够“继承”它所转发消息的对象的某些特征。

Important(划重点)


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.

译:为了响应对象本身不能识别的方法,您必须重写methodSignatureForSelector:forwardInvocation:。转发消息的机制使用methodSignatureForSelector:获得的信息来创建要转发的NSInvocation对象。重写方法必须为给定的选择器提供适当的方法签名,可以通过预先构造一个选择器,也可以通过向另一个对象请求一个选择器。

显然methodSignatureForSelectorforwardInvocation是要一起出现的,下面我们通过一个示例来演示如何使用这两个方法来实现消息的转发。

举个例子:

还是刚才的工程,注释掉forwardingTargetForSelector的实现。

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

打印结果如下:

打印结果

可以看到,通过以上代码的处理saySomething消息也被转发了。其实当我们注释了forwardInvocation内部实现,也不会导致崩溃。

(文档上的其他注意点总结)其他注意点:

  1. forwardInvocation可以查找响应anInvocation中的编码的消息对象,对于所有消息,此对象不必相同
  2. 使用anInvocation将消息发送到该对象时anInvocation将保存结果,运行时系统将提取结果并将其传递给原始发送者
  3. forwardInvocation方法的实现不仅仅可以转发消息,还可以合并响应各种不同消息的代码,从而避免为每个选择器编写单独方法的麻烦。
  4. forwardInvocation方法对给定消息的响应中不仅将其转发给一个对象,还有可能涉及其他几个对象
  5. forwardInvocationNSObject的方法,并且只会调用doesNotRecognizeSelector方法,如果不实现doesNotRecognizeSelector它不会转发任何消息从而引起异常。

2.6 消息转发流程图

从动态方法决议到消息的快速转发,再到消息的慢速转发流程如下:


消息转发流程图

至此我们的消息转发流程基本完毕

3. 总结

  1. 动态方法决议有对象方法动态解析resolveInstanceMethod和类方法动态解析resolveClassMethod两种,都需要开发者去实现
  2. 消息转发同样分为快速消息转发forwardingTargetForSelector和慢速消息转发methodSignatureForSelector
  3. 慢速消息转发同时还需要开发者实现forwardInvocation方法
  4. 快速消息转发是让其他能响应的对象来响应未查找到的消息,对参数和返回值要求绝对匹配
  5. 慢速消息转发提供了更加细粒度的控制,首先会返回一个方法签名给runtime,然后通过anInvocation保存结果,Runtime会提取结果并将其传递给原始发送者


至此我们的消息或者说方法,在Objective-C的底层实现由objc_msgSend开始,探索了消息发送的流程,然后由消息找不到时的处理进入到了动态方法决议,然后通过_objc_msgForward_impcache进入到消息的转发流程就结束了,探索过程比较粗糙,也会有些瑕疵,如有问题欢迎指正。。

你可能感兴趣的:(iOS Objective-C 消息的转发)