美文网首页
OC源码分析之方法的解析与转发原理

OC源码分析之方法的解析与转发原理

作者: 红酒牛排CC | 来源:发表于2020-04-14 18:00 被阅读0次
    img_cover@1x.jpg

    前言

    想要成为一名iOS开发高手,免不了阅读源码。以下是笔者在OC源码探索中梳理的一个小系列——类与对象篇,欢迎大家阅读指正,同时也希望对大家有所帮助。

    1. OC源码分析之对象的创建
    2. OC源码分析之isa
    3. OC源码分析之类的结构解读
    4. OC源码分析之方法的缓存原理
    5. OC源码分析之方法的查找原理
    6. OC源码分析之方法的解析与转发原理

    OC中方法的调用是通过objc_msgSend(或objc_msgSendSuper,或objc_msgSend_stret,或objc_msgSendSuper_stret)函数,向调用者发送名为SEL的消息,找到具体的函数地址IMP,进而执行该函数。如果找不到IMP,会进行方法的解析,这相当于提供一次容错处理;方法解析之后,如果依然找不到IMP,还有最后一次机会,那就是消息的转发。

    方法的查找流程尽在 OC源码分析之方法的查找原理 一文中,文接此文,本文将深入剖析方法的解析与转发。

    下面进入正题。

    需要注意的是,笔者用的源码是 objc4-756.2

    1 方法的解析

    方法的解析,即method resolver(又名消息的解析,也叫方法决议),其建立在方法的查找的失败结果上,入口源码如下:

        // 在【类...根类】的【缓存+方法列表】中都没找到IMP,进行方法解析
        if (resolver  &&  !triedResolver) {
            runtimeLock.unlock();
            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;
        }
    

    它主要是调用了resolveMethod函数。resolveMethod函数处理完毕之后,还要重新执行一次retry(再走一遍方法的查找流程)。其中,triedResolver这个变量使得消息的解析只进行一次。

    1.1 resolveMethod

    且看resolveMethod函数源码:

    static void resolveMethod(Class cls, SEL sel, id inst)
    {
        runtimeLock.assertUnlocked();
        assert(cls->isRealized());
    
        if (! cls->isMetaClass()) {
            // try [cls resolveInstanceMethod:sel]
            resolveInstanceMethod(cls, sel, inst);
        } 
        else {
            // try [nonMetaClass resolveClassMethod:sel]
            // and [cls resolveInstanceMethod:sel]
            resolveClassMethod(cls, sel, inst);
            if (!lookUpImpOrNil(cls, sel, inst, 
                                NO/*initialize*/, YES/*cache*/, NO/*resolver*/)) 
            {
                resolveInstanceMethod(cls, sel, inst);
            }
        }
    }
    

    这里有两个分支,主要是对cls做个是否元类的判断:

    • 不是元类,意味着调用的是实例方法,那么执行resolveInstanceMethod函数
    • 是元类,说明调用的是类方法,执行resolveClassMethod函数,之后如果依然没找到IMP,则再去执行resolveInstanceMethod函数;

    先看实例方法的情况

    1.2 实例方法解析

    resolveInstanceMethod源码如下:

    static void resolveInstanceMethod(Class cls, SEL sel, id inst)
    {
        runtimeLock.assertUnlocked();
        assert(cls->isRealized());
    
        if (! lookUpImpOrNil(cls->ISA(), SEL_resolveInstanceMethod, cls, 
                             NO/*initialize*/, YES/*cache*/, NO/*resolver*/)) 
        {
            // 如果你没有实现类方法 +(BOOL)resolveInstanceMethod:(SEL)sel
            // NSObject也有实现,所以一般不会走这里
            // 注意这里传入的第一个参数是:cls->ISA(),也就是元类
            return;
        }
    
        BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
        // 调用类方法: +(BOOL)resolveInstanceMethod:(SEL)sel
        bool resolved = msg(cls, SEL_resolveInstanceMethod, sel);
    
        // 再找一次imp(这次是sel,而不是resolveInstanceMethod)
        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));
            }
        }
    }
    

    resolveInstanceMethod函数先后调用了两次lookUpImpOrNil

    • 第一次的调用是判断类(包括其父类,直至根类)是否实现了+(BOOL)resolveInstanceMethod:(SEL)sel类方法
      • SEL_resolveInstanceMethod相当于@selector(resolveInstanceMethod:)NSObject类中有实现这个类方法(返回的是NO,会影响是否打印),所以一般会接着往下走。
    • 第二次的调用的目的是检测是否有sel对应的IMP。假如你在+(BOOL)resolveInstanceMethod:(SEL)sel中添加了sel的函数地址IMP,此时再次去查找这个IMP就能找到。

    注意到这两次调用中,resolver都是NO,因此在其调用lookUpImpOrForward时不会触发 消息的解析,仅仅是从“类、父类、...、根类”的缓存中和方法列表中找IMP,没找到会触发 消息转发

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

    这里会判断IMP是否是消息转发而来的,如果是,就不返回。

    1.3 类方法解析

    类方法的解析首先是调用resolveClassMethod函数,其源码如下:

    // 这里的cls是元类,因为类方法存储在元类
    static void resolveClassMethod(Class cls, SEL sel, id inst)
    {
        runtimeLock.assertUnlocked();
        assert(cls->isRealized());
        assert(cls->isMetaClass());
    
        if (! lookUpImpOrNil(cls, SEL_resolveClassMethod, inst, 
                             NO/*initialize*/, YES/*cache*/, NO/*resolver*/)) 
        {
            // 如果你没有实现类方法 +(BOOL)resolveClassMethod:(SEL)sel
            // NSObject也有实现,所以一般不会走这里
            // 注意这里的第一个参数是cls,是元类
            return;
        }
    
        Class nonmeta;
        {
            mutex_locker_t lock(runtimeLock);
            // 获取 元类的对象,即类。换句话说,nonmeta 也就是 inst
            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)resolveClassMethod:(SEL)sel
        bool resolved = msg(nonmeta, SEL_resolveClassMethod, sel);
    
        // 再找一次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 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函数大体相同,需要留意的是,这次判断类(包括其父类,直至根类)是否实现的是+(BOOL)resolveClassMethod:(SEL)sel类方法。

    让我们回顾一下resolveMethod函数对类方法的解析

    // try [nonMetaClass resolveClassMethod:sel]
    // and [cls resolveInstanceMethod:sel]
    resolveClassMethod(cls, sel, inst);
    if (!lookUpImpOrNil(cls, sel, inst, 
            NO/*initialize*/, YES/*cache*/, NO/*resolver*/)) 
    {
        // 此时的cls为元类,也就是 NSObject 调用 resolveInstanceMethod:
        resolveInstanceMethod(cls, sel, inst);
    }
    

    在经过resolveClassMethod的处理之后,如果依然没有找到类方法的IMP,就会再次执行resolveInstanceMethod函数!不同于实例方法的是,此时的cls是元类,因此msg(cls, SEL_resolveInstanceMethod, sel);即是向元类内部发送resolveInstanceMethod:消息,也就意味着是根类调用resolveInstanceMethod:方法(这次只能在根类的分类中补救了),同时缓存查找类方法的IMP仅发生在根元类和根类中,而方法列表中查找类方法的IMP则分别在“元类、元类的父类、...、根元类、根类”中进行。

    简而言之,当我们调用一个类方法时,如果在类中没有实现,同时在resolveClassMethod中也没有处理,那么最终会调用根类(NSObject)的同名实例方法

    1.4 举个栗子

    通过上述的分析,相信大家对方法的解析有了一定的认知,下面我们来整个简单的例子消化一下。

    @interface Person : NSObject
    
    + (void)personClassMethod1;
    - (void)personInstanceMethod1;
    
    @end
    
    @implementation Person
    
    @end
    

    一个简单的Person类,里面分别有一个类方法和一个实例方法,但是都没有实现。

    接着添加对这两个方法的解析:

    - (void)unimplementedMethod:(SEL)sel {
        NSLog(@"没实现?没关系,绝不崩溃");
    }
    
    + (BOOL)resolveInstanceMethod:(SEL)sel {
        NSLog(@"动态实例方法解析:%@", NSStringFromSelector(sel));
        if (sel == @selector(personInstanceMethod1)) {
            IMP methodIMP = class_getMethodImplementation(self, @selector(unimplementedMethod:));
            Method method = class_getInstanceMethod(Person.class, @selector(unimplementedMethod:));
            const char *methodType = method_getTypeEncoding(method);
            return class_addMethod(Person.class, sel, methodIMP, methodType);
        }
        return [super resolveInstanceMethod:sel];
    }
    
    + (BOOL)resolveClassMethod:(SEL)sel {
        NSLog(@"动态类方法解析:%@", NSStringFromSelector(sel));
        if (sel == @selector(personClassMethod1)) {
            IMP methodIMP = class_getMethodImplementation(self, @selector(unimplementedMethod:));
            Method method = class_getInstanceMethod(Person.class, @selector(unimplementedMethod:));
            const char *methodType = method_getTypeEncoding(method);
            return class_addMethod(objc_getMetaClass("Person"), sel, methodIMP, methodType);
        }
        return [super resolveClassMethod:sel];
    }
    

    看看打印:

    image

    通过对类方法解析的源码分析,我们知道,也可以把对Person类方法的处理放在NSObject分类的resolveClassMethod:resolveInstanceMethod:中,都能达到相同的效果(记得把Person类中的resolveClassMethod:处理去掉)。这里略过不提。

    2 消息转发

    方法的调用经过了查找、解析,如果还是没有找到IMP,就会来到消息转发流程。它的入口在lookUpImpOrForward函数靠后的位置

    // 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是汇编函数,以arm64架构为例,其源码如下:

    STATIC_ENTRY __objc_msgForward_impcache
    
    // No stret specialization.
    b   __objc_msgForward
    
    END_ENTRY __objc_msgForward_impcache
    

    __objc_msgForward_impcache内部调用了__objc_msgForward

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

    这个函数主要做的事情是,通过页地址与页地址偏移的方式,拿到_objc_forward_handler函数的地址并调用。

    说明:

    • adrp是以页为单位的大范围的地址读取指令,这里的p就是page的意思
    • ldr类似与movmvn,当立即数(__objc_msgForward中是[x17, __objc_forward_handler@PAGEOFF]PAGEOFF是页地址偏移值)大于movmvn能操作的最大数时,就使用ldr

    OBJC2中,_objc_forward_handler实际上就是objc_defaultForwardHandler函数,其源码如下:

    // Default forward handler halts the process.
    __attribute__((noreturn)) void 
    objc_defaultForwardHandler(id self, SEL sel)
    {
        _objc_fatal("%c[%s %s]: unrecognized selector sent to instance %p "
                    "(no message forward handler is installed)", 
                    class_isMetaClass(object_getClass(self)) ? '+' : '-', 
                    object_getClassName(self), sel_getName(sel), self);
    }
    void *_objc_forward_handler = (void*)objc_defaultForwardHandler;
    

    是不是很熟悉?当我们调用一个没实现的方法时,报的错就是“unrecognized selector sent to ...”

    但是问题来了,说好的消息转发流程呢?这才刚开始怎么就结束了?不急,憋慌,且看下去。

    2.2 消息转发的调用栈

    回顾方法解析时举的例子,不妨把解析的内容去掉,Let it crash!

    image

    发现在崩溃之前与消息转发相关的内容是,调用了_CF_forwarding_prep_0___forwarding___这两个函数。遗憾的是这两个函数并未开源。

    既然崩溃信息不能提供帮助,只好打印具体的调用信息了。

    在方法的查找流程中,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 (slowpath(objcMsgLogEnabled && implementer)) {
            bool cacheIt = logMessageSend(implementer->isMetaClass(), 
                                          cls->nameForLogging(),
                                          implementer->nameForLogging(), 
                                          sel);
            if (!cacheIt) return;
        }
    #endif
        cache_fill(cls, sel, imp, receiver);
    }
    
    bool objcMsgLogEnabled = false;
    
    // Define SUPPORT_MESSAGE_LOGGING to enable NSObjCMessageLoggingEnabled
    #if !TARGET_OS_OSX
    #   define SUPPORT_MESSAGE_LOGGING 0
    #else
    #   define SUPPORT_MESSAGE_LOGGING 1
    #endif
    

    打印的关键函数就是logMessageSend,但是它受SUPPORT_MESSAGE_LOGGINGobjcMsgLogEnabled控制。

    继续跟进SUPPORT_MESSAGE_LOGGING

    #if !DYNAMIC_TARGETS_ENABLED
        #define TARGET_OS_OSX               1
        ...
    #endif
        
    #ifndef DYNAMIC_TARGETS_ENABLED
     #define DYNAMIC_TARGETS_ENABLED   0
    #endif
    

    从源码不难看出TARGET_OS_OSX的值是1,因此,SUPPORT_MESSAGE_LOGGING也为1!

    如果能把objcMsgLogEnabled改成true,显然就可以打印调用信息了。通过全局搜索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;
    }
    

    接下来就好办了!来到main.m,添加以下代码

    extern void instrumentObjcMessageSends(BOOL flag);
    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            instrumentObjcMessageSends(true);
            [Person personClassMethod1];
            instrumentObjcMessageSends(false);
        }
        return 0;
    }
    

    运行工程,直到再次崩溃。此时已打印函数调用栈,日志文件位置在logMessageSend函数中有标注

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

    打开Finder(访达),cmd + shift + G快捷键,输入/tmp/msgSends,找到最新的一份日志文件(数字最大)

    image

    打印结果如下:

    image

    从这份日志可以看出,与转发相关的方法是forwardingTargetForSelectormethodSignatureForSelector,分别对应了消息的快速转发流程和慢速转发流程,接下来开始分析这两个方法。

    2.3 消息的快速转发

    forwardingTargetForSelector:对应的就是消息的快速转发流程,它在源码中只是简单的返回nil(可在子类或分类中重写)

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

    不过我们可以在开发文档中找到说明(cmd + shift + 0快捷键)

    image

    概括地说,forwardingTargetForSelector:主要是返回一个新的receiver,去处理sel这个当前类无法处理的消息,如果处理不了,会转到效率低下的forwardInvocation:。在效率方面,forwardingTargetForSelector:领先forwardInvocation:一个数量级,因此,最好不要用后者的方式处理消息的转发逻辑。

    关于forwardingTargetForSelector:返回的新的receiver,需要注意一下几点:

    • 绝对不能返回self,否则会陷入无限循环;
    • 不处理的话,可以返回nil,或者[super forwardingTargetForSelector:sel](非根类的情况),此时会走methodSignatureForSelector:慢速转发流程;
    • 如果有这个receiver,此时相当于执行objc_msgSend(newReceiver, sel, ...),那么它必须拥有和被调用的方法相同方法签名的方法(方法名、参数列表、返回值类型都必须一致)。

    2.3.1 举个栗子

    我们可以实验一下,准备工作如下

    @interface ForwardObject : NSObject
    
    @end
    
    @implementation ForwardObject
    
    + (void)personClassMethod1 {
        NSLog(@"类方法转发给%@,执行%s", [self className], __FUNCTION__);
    }
    
    - (void)personInstanceMethod1 {
        NSLog(@"实例方法转发给%@,执行%s", [self className], __FUNCTION__);
    }
    
    @end
    
    @interface Person : NSObject
    
    + (void)personClassMethod1;
    - (void)personInstanceMethod1;
    
    @end
    
    @implementation Person
    
    - (id)forwardingTargetForSelector:(SEL)aSelector {
        NSLog(@"实例方法开始转发");
        return [ForwardObject alloc];
    }
    
    + (id)forwardingTargetForSelector:(SEL)sel {
        NSLog(@"类方法开始转发");
        return [ForwardObject class];
    }
    
    @end
    

    显然,ForwardObject作为消息转发后的处理类,拥有Person类的同名类方法和实例方法。现在开始验证,结果如下:

    image

    事实证明确实有效!接下来看消息的慢速转发流程。

    2.4 消息的慢速转发

    如果forwardingTargetForSelector:没有处理消息(如返回nil),就会启动慢速转发流程,也就是methodSignatureForSelector:方法,同样需要在子类或分类中重写

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

    通过阅读官方文档,我们得出以下结论:

    • methodSignatureForSelector:方法是跟forwardInvocation:方法搭配使用的,前者需要我们根据sel返回一个方法签名,后者会把这个方法签名封装成一个NSInvocation对象,并将其作为形参。
    • 如果有目标对象能处理Invocation中的selInvocation可以指派这个对象处理;否则不处理。
      • Invocation可以指派多个对象处理

    注意:消息的慢速转发流程性能较低,如果可以的话,你应该尽可能早地处理掉消息(如在方法解析时,或在消息的快速转发流程时)。

    2.4.1 举个栗子

    针对慢速流程,同样可以验证。这里把快速转发例子中的Person类修改一下:

    @implementation Person
    
    // MARK: 慢速转发--类方法
    
    + (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
        NSLog(@"类方法慢速转发:%s, sel:%@", __FUNCTION__, NSStringFromSelector(aSelector));
        if (aSelector == @selector(personClassMethod1)) {
            return [NSMethodSignature signatureWithObjCTypes:"v@:"];
        }
        return [super methodSignatureForSelector:aSelector];
    }
    
    + (void)forwardInvocation:(NSInvocation *)anInvocation {
        SEL aSelector = [anInvocation selector];
        NSLog(@"类方法慢速转发:%s, sel:%@", __FUNCTION__, NSStringFromSelector(aSelector));
        id target = [ForwardObject class];
        if ([target respondsToSelector:aSelector]) [anInvocation invokeWithTarget:target];
        else [super forwardInvocation:anInvocation];
    }
    
    // MARK: 慢速转发--实例方法
    
    - (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
        NSLog(@"实例方法慢速转发:%s, sel:%@", __FUNCTION__, NSStringFromSelector(aSelector));
        if (aSelector == @selector(personInstanceMethod1)) {
            return [NSMethodSignature signatureWithObjCTypes:"v@:"];
        }
        return [super methodSignatureForSelector:aSelector];
    }
    
    - (void)forwardInvocation:(NSInvocation *)anInvocation {
        SEL aSelector = [anInvocation selector];
        NSLog(@"实例方法慢速转发:%s, sel:%@", __FUNCTION__, NSStringFromSelector(aSelector));
        ForwardObject *obj = [ForwardObject alloc];
        if ([obj respondsToSelector:aSelector]) [anInvocation invokeWithTarget:obj];
        else [super forwardInvocation:anInvocation];
    }
    
    @end
    

    其结果如下图所示,显然也没有崩溃。

    image

    对方法签名不熟悉的可以查看 苹果官方的类型编码介绍

    3 总结

    综上所述,当我们调用方法时,首先进行方法的查找,如果查找失败,会进行方法的解析,此时OC会给我们一次对sel的处理机会,你可以在resolveInstanceMethod:(类方法对应resolveClassMethod:)中添加一个IMP;如果你没把握住这次机会,也就是解析失败时,会来到消息转发阶段,这个阶段有两个机会去处理sel,分别是快速转发的forwardingTargetForSelector:,以及慢速转发的methodSignatureForSelector:。当然,如果这些机会你都放弃了,那OC只好让程序崩溃。

    下面用一副图总结方法的解析和转发流程

    image

    4 问题讨论

    4.1 为什么引入消息转发机制?

    在一个方法被调用之前,我们是没办法确定它的实现地址的,直到运行时,这个方法被调用的时候,我们才能真正知道它是否有实现,以及其具体的实现地址。这也就是所谓的“动态绑定”。

    在编译期,如果编译器发现方法不存在,会直接报错;同样,在运行时,也有doesNotRecognizeSelector的处理。

    在抛出doesNotRecognizeSelector这个异常信息之前,OC利用其动态绑定的特性,引入了消息转发机制,给予了我们额外的机会处理消息(解析 or 转发),这样的做法显然更加周全合理。

    5 PS

    相关文章

      网友评论

          本文标题:OC源码分析之方法的解析与转发原理

          本文链接:https://www.haomeiwen.com/subject/dintvhtx.html