iOS底层-消息转发

前言

上篇 动态方法决议中,如果在动态方法决议也没有找到imp,动态方法决议会返回imp = _objc_msgForward_impcache,进行消息转发,那么消息是如何转发的?下面具体探索消息转发的原理。

instrumentObjcMessageSends 分析

创建一个Command Line Tool的新Project,并创建一个对象ZLObject

@interface ZLObject : NSObject

- (void)instanceMethod;

@end

@implementation ZLObject

@end
复制代码

声明一个函数instrumentObjcMessageSends打印系统调用的方法的列表,调用和声明方式如下:

extern void instrumentObjcMessageSends(BOOL flag);

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        ZLObject *obj = [ZLObject alloc];
        instrumentObjcMessageSends(YES);
        [obj instanceMethod];
        instrumentObjcMessageSends(NO);
    }
    return 0;
}
复制代码

执行结果:

-[ZLObject instanceMethod]: unrecognized selector sent to instance 0x10792cc40
复制代码

instrumentObjcMessageSends 源码

void instrumentObjcMessageSends(BOOL flag)
{
    bool enable = flag;
    // 如果设置的 enable 和之前的 objcMsgLogEnabled 相同,无需再次设置。
    // objcMsgLogEnabled 默认为 false
    if (objcMsgLogEnabled == enable)
        return;
    // 如果允许打印,则冲洗所有方法缓存,以便获得一些跟踪
    if (enable)
        _objc_flush_caches(Nil);
    // 如果不等于-1,说明本地有日志文件,则同步日志文件
    if (objcMsgLogFD != -1)
        fsync (objcMsgLogFD);
    // 是否允许
    objcMsgLogEnabled = enable;
}
复制代码

objcMsgLogEnabled是打印日志的标记,而该值就是从instrumentObjcMessageSends方法传入的参数值。

objcMsgLogEnabled

全局搜索objcMsgLogEnabled,在慢速查找lookUpImpOrForward流程中,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 (slowpath(objcMsgLogEnabled && implementer)) {
        // 打印发送消息日志
        bool cacheIt = logMessageSend(implementer->isMetaClass(), 
                                      cls->nameForLogging(),
                                      implementer->nameForLogging(), 
                                      sel);
        if (!cacheIt) return;
    }
#endif
    // 插入缓存
    cls->cache.insert(sel, imp, receiver);
}
复制代码

objcMsgLogEnabledture时,会执行logMessageSend方法。

logMessageSend

bool logMessageSend(bool isClassMethod, const char *objectsClass, const char *implementingClass, SEL selector)
{
    char buf[ 1024 ];
    // 创建/打开日志文件
    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;
        }
    }
    // 创建要插入的日志条目
    snprintf(buf, sizeof(buf), "%c %s %s %s\n",
            isClassMethod ? '+' : '-',
            objectsClass,
            implementingClass,
            sel_getName(selector));

    objcMsgLogLock.lock();
    // 写入日志
    write (objcMsgLogFD, buf, strlen(buf));
    objcMsgLogLock.unlock();

    return false;
}
复制代码

如果开启缓存,会在/tmp/msgSends-%d中写下日志:

msgSends前缀的文件都删除,再次执行上面的案例代码,再次查看缓存文件:

结论

  • resolveInstanceMethod动态方法决议,如果找不到imp,则进行消息转发。
  • 消息转发流程如下:
    • forwardingTargetForSelector
    • methodSignatureForSelector
    • resolveInstanceMethod
    • doesNotRecognizeSelector

forwardingTargetForSelector 快速消息转发

objc_msgSend动态方法决议来看,当调用没有实现的方法imp时,在进行快速查找,慢速查找,动态方法决议后,会返回imp = _objc_msgForward_impcache,进行消息转发。首先进入消息快速转发forwardingTargetForSelector方法。

源码如下:

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

- (id)forwardingTargetForSelector:(SEL)sel {
    return nil;
}
复制代码

特点:

  • 返回值是对象,将用作新的接收方对象,消息调度将转移到该新对象中。换句话说,当前类调用没有实现的方法时,调度到新的对象中去。
  • 返回值默认是nil,意思为不执行重定向。
  • 返回值不能self,会造成死循环。
  • 如果在非NSObject中实现,使用super实现返回的结果。
  • 比常规转发快一个数量级
  • 如果转发的目标是NSInvocation,或在转发过程中操纵参数或返回值,则此方法将不起作用。

forwardingTargetForSelector 案例

接着上面instrumentObjcMessageSends的案例,改进如下:

@interface ZLObject : NSObject

+ (void)classMethod;
- (void)instanceMethod;

@end

@implementation ZLObject

+ (id)forwardingTargetForSelector:(SEL)aSelector {
    NSLog(@"%s - %@",__func__,NSStringFromSelector(aSelector));
    return [super forwardingTargetForSelector:aSelector];
}
- (id)forwardingTargetForSelector:(SEL)aSelector {
    NSLog(@"%s - %@",__func__,NSStringFromSelector(aSelector));
    return [super forwardingTargetForSelector:aSelector];
}

@end
复制代码

分别执行对象方法和类方法:

ZLObject *obj = [ZLObject alloc];
[obj instanceMethod];

[ZLObject classMethod];
复制代码

分别打印执行结果:

-[ZLObject forwardingTargetForSelector:] - instanceMethod
-[ZLObject instanceMethod]: unrecognized selector sent to instance 0x10050cae0

+[ZLObject forwardingTargetForSelector:] - classMethod
+[ZLObject classMethod]: unrecognized selector sent to class 0x100008270
复制代码

说明:在forwardingTargetForSelector对象方法和类方法中,可以捕获到异常方法。

forwardingTargetForSelector 重定向

添加一个转发类ZLForwardObject类:

@interface ZLForwardObject : NSObject

+ (void)classMethod;
- (void)instanceMethod;

@end

@implementation ZLForwardObject

+ (void)classMethod {
    NSLog(@"%s",__func__);
}

- (void)instanceMethod {
    NSLog(@"%s",__func__);
}

@end
复制代码

改进ZLObject类:

+ (id)forwardingTargetForSelector:(SEL)aSelector {
    NSLog(@"%s - %@",__func__,NSStringFromSelector(aSelector));
    if (aSelector == @selector(classMethod)) {
        return [ZLForwardObject class];
    }
    return [super forwardingTargetForSelector:aSelector];
}

- (id)forwardingTargetForSelector:(SEL)aSelector {
    NSLog(@"%s - %@",__func__,NSStringFromSelector(aSelector));
    if (aSelector == @selector(instanceMethod)) {
        return [ZLForwardObject alloc];
    }
    return [super forwardingTargetForSelector:aSelector];
}
复制代码

重新执行对象方法和类方法:

-[ZLObject forwardingTargetForSelector:] - instanceMethod
-[ZLForwardObject instanceMethod]

+[ZLObject forwardingTargetForSelector:] - classMethod
+[ZLForwardObject classMethod]
复制代码

这样就重定向到ZLForwardObject中,执行ZLForwardObject中的对象方法和类方法了。

总结

重定向的方法名必须和捕获到的方法名相同,否则无法重定向成功。例如案例中的ZLObject执行的instanceMethodclassMethod方法名,在重定向ZLForwardObject的类中,方法名要相同。

methodSignatureForSelector 慢速消息转发

源码如下:

+ (NSMethodSignature *)methodSignatureForSelector:(SEL)sel {
    _objc_fatal("+[NSObject methodSignatureForSelector:] "
                "not available without CoreFoundation");
}

- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel {
    _objc_fatal("-[NSObject methodSignatureForSelector:] "
                "not available without CoreFoundation");
}
复制代码

特点:

  • 返回值是NSMethodSignature对象,该对象包含:由给定SEL的方法标识的描述。
  • 用于协议的实现。
  • 在消息转发期间,必须创建NSInvocation对象。
  • 如果对象维持一个代理,或者处理它未实现的消息时,则应重写此方法以返回适当的方法签名。

其中消息转发时,必须实现forwardInvocation方法:

+ (void)forwardInvocation:(NSInvocation *)invocation {
    [self doesNotRecognizeSelector:(invocation ? [invocation selector] : 0)];
}

- (void)forwardInvocation:(NSInvocation *)invocation {
    [self doesNotRecognizeSelector:(invocation ? [invocation selector] : 0)];
}
复制代码

methodSignatureForSelector 案例

注意: 如果使用快速转发的案例,需将快速转发的方法注释掉,或者将其返回nil

+ (NSMethodSignature *)methodSignatureForSelector:(SEL)sel {
    NSLog(@"%s - %@",__func__,NSStringFromSelector(sel));
    return [super methodSignatureForSelector:sel];
}

- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel {
    NSLog(@"%s - %@",__func__,NSStringFromSelector(sel));
    return [super methodSignatureForSelector:sel];
}
复制代码

分别执行对象方法和类方法:

-[ZLObject methodSignatureForSelector:] - instanceMethod
-[ZLObject instanceMethod]: unrecognized selector sent to instance 0x10072db80

+[ZLObject methodSignatureForSelector:] - classMethod
+[ZLObject classMethod]: unrecognized selector sent to class 0x100008290
复制代码

methodSignatureForSelector 实现方法签名

+ (NSMethodSignature *)methodSignatureForSelector:(SEL)sel {
    NSLog(@"%s - %@",__func__,NSStringFromSelector(sel));
    if (sel == @selector(classMethod)) {
        Method m = class_getClassMethod([ZLForwardObject class], @selector(classMethod));
        const char * type = method_getTypeEncoding(m);
        return [NSMethodSignature signatureWithObjCTypes:type];
    }
    return [super methodSignatureForSelector:sel];
}

+ (void)forwardInvocation:(NSInvocation *)anInvocation {
    NSLog(@"%s - %@ - %@", __func__, anInvocation.target, NSStringFromSelector(anInvocation.selector));
}

- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel {
    NSLog(@"%s - %@",__func__,NSStringFromSelector(sel));
    if (sel == @selector(instanceMethod)) {
        Method m = class_getInstanceMethod([ZLForwardObject class], @selector(instanceMethod));
        const char * type = method_getTypeEncoding(m);
        return [NSMethodSignature signatureWithObjCTypes:type];
    }
    return [super methodSignatureForSelector:sel];
}

- (void)forwardInvocation:(NSInvocation *)anInvocation {
    NSLog(@"%s - %@ - %@", __func__, anInvocation.target, NSStringFromSelector(anInvocation.selector));
}
复制代码

再次调用对象方法和类方法:

-[ZLObject methodSignatureForSelector:] - instanceMethod
-[ZLObject forwardInvocation:] - <ZLObject: 0x107925df0> - instanceMethod

+[ZLObject methodSignatureForSelector:] - classMethod
+[ZLObject forwardInvocation:] - ZLObject - classMethod
复制代码

这样虽然实现了消息代理,并且forwardInvocation也有了回调,但是新方法没有处理,换句话说就是事务没有处理。(方法的调用都是消息发送,这些消息就是事务,可实现可不实现。)

实现forwardInvocation的事务处理:

+ (void)forwardInvocation:(NSInvocation *)anInvocation {
    Class fClass = [ZLForwardObject class];
    if ([self respondsToSelector:anInvocation.selector]) {
        // 自己能够响应
        [anInvocation invoke];
    } else if ([fClass respondsToSelector:anInvocation.selector]) {
        // 消息重定向
        [anInvocation invokeWithTarget:fClass];
    } else {
        // 可以在这里上报错误
        [super forwardInvocation:anInvocation];
    }
}

- (void)forwardInvocation:(NSInvocation *)anInvocation {
    ZLForwardObject *fObj = [ZLForwardObject alloc];
    if ([self respondsToSelector:anInvocation.selector]) {
        // 自己能够响应
        [anInvocation invoke];
    } else if ([fObj respondsToSelector:anInvocation.selector]) {
        // 消息重定向
        [anInvocation invokeWithTarget:fObj];
    } else {
        // 可以在这里上报错误
        [super forwardInvocation:anInvocation];
    }
}
复制代码

重新调用对象方法和类方法:

-[ZLObject methodSignatureForSelector:] - instanceMethod
-[ZLForwardObject instanceMethod]

+[ZLObject methodSignatureForSelector:] - classMethod
+[ZLForwardObject classMethod]
复制代码

总结

  • methodSignatureForSelector必须与forwardInvocation成对出现一起使用。
  • 慢速消息转发可以根据自身业务和需求灵活处理。
  • 相对于快速转发给了很大的灵活性。
  • 如果提炼出来在NSObject分类中实现,对于OC方法找不到的崩溃都能避免掉。但是这只是假象的消失,造成了很多资源的浪费。因为能执行到这一步必然经历了很多流程(快速查找→慢速查找→动态方法决议→快速转发→慢速转发

doesNotRecognizeSelector

当慢速转发也找不到重定向机制的话,最后进入doesNotRecognizeSelector

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

- (void)doesNotRecognizeSelector:(SEL)sel {
    _objc_fatal("-[%s %s]: unrecognized selector sent to instance %p", 
                object_getClassName(self), sel_getName(sel), self);
}
复制代码
  • 该方法并不能处理事务,只是让异常可控,拿到错误信息。
  • 这也是在日常开发中遇到方法崩溃,打印的崩溃信息的出处。

消息转发的汇编分析

下面将通过汇编的方式探索消息转发流程。将上面案例中的快速转发和慢速转发的方法注释掉,执行。崩溃后bt打印:

通过分析消息流程:startmain.m_CF_forwarding_prep_0___forwarding___doesNotRecognizeSelector。其中_CF_forwarding_prep_0___forwarding___才是探究的研究方向。并且都属于CoreFoundation的库。

CoreFoundation

通过 opensource 下载CoreFoundation源码

但是去搜索_CF_forwarding_prep_0___forwarding___相关字段时,并没有在CoreFoundation源码中搜索到。这说明苹果对于这部分的内容没有开源

CoreFoundation库的获取

  • 新建一个可以真机或者模拟器的Project,执行image list获取镜像列表,并获取CoreFoundation库。

  • 根据查找到的库链接,找到对应的CoreFoundation可执行文件

  • 使用Hopper打开找到的CoreFoundation可执行文件(以arm64为例),搜索forwarding字段:

至此,_forwarding_prep_0___forwarding___相关字段就找到了。下面具体分析各自的作用。

__forwarding_prep_0分析

int ___forwarding_prep_0___(int arg0, int arg1, int arg2, int arg3, int arg4, int arg5) {
    // 转发流程
    rax = ____forwarding___(rsp, 0x0);
    if (rax != 0x0) {
        rax = *rax;
    } else {
        // 如果消息转发没实现
        rax = objc_msgSend(arg0, arg1);
    }
    return rax;
}
复制代码
  • ____forwarding___方法之前,是拉伸栈空间、操作寄存器存储一些数据
  • ____forwarding___的返回值r0,如果不存在,执行objc_msgSend,作为返回值。

__forwarding__伪代码分析

int ____forwarding___(int arg0, int arg1) {
    SEL sel = arg1;
    Class obj = arg0;
    Class cls = object_getClass(obj);
    const char * className = class_getName(cls);
    forwardingTargetForSelector(cls, sel, className);
    return 0;
}

void forwardingTargetForSelector(Class cls, SEL sel, const char * className) {
    // 是否能响应 forwardingTargetForSelector
    if (class_respondsToSelector(cls, @selector(forwardingTargetForSelector:))) {
        id obj = [cls forwardingTargetForSelector:sel];
        if (obj) {
            // 返回 forwardingTargetForSelector 消息接收者
            return obj;
        } else {
            SEL currentSel = @selector(methodSignatureForSelector:);
            methodSignatureForSelector(cls, currentSel);
        }
    } else {
        SEL currentSel = @selector(methodSignatureForSelector:);
        methodSignatureForSelector(cls, currentSel);
    }
}

void methodSignatureForSelector(Class cls, SEL sel) {
    // 是否野指针
    if (strncmp(className, "_NSZombie_", 0xa) == 0x0) {
        Class cls = class_getSuperclass(cls);
        SEL currentSel = @selector(doesNotRecognizeSelector:);
        doesNotRecognizeSelector(cls, currentSel);
    } else {
        if (class_respondsToSelector(cls, @selector(methodSignatureForSelector:))) {
            NSMethodSignature *signature = [cls methodSignatureForSelector:sel];
            if (signature) {
                _forwardStackInvocation(cls, signature);
            } else {
                doesNotRecognizeSelector(cls, sel);
            }
        } else {
            doesNotRecognizeSelector(cls, sel);
        }
    }
}

void _forwardStackInvocation(Class cls, NSMethodSignature *signature) {
    if (class_respondsToSelector(cls, @selector(_forwardStackInvocation:))) {
        // 执行dispatch_once相关逻辑
        NSInvocation *invocation = [NSInvocation requiredStackSizeForSignature:signature];
        void *bytes;
        NSInvocation *inv = [invocation _initWithMethodSignature:signature frame:NULL buffer:NULL size:bytes];
        [cls _forwardStackInvocation:invocation];
    } else {
        forwardInvocation(cls, signature);
    }
}

void forwardInvocation(Class cls, NSMethodSignature *signature) {
    if (class_respondsToSelector(cls, @selector(forwardInvocation:))){
        NSInvocation *invocation = [NSInvocation _invocationWithMethodSignature:signature frame:NULL];
        [cls forwardInvocation:invocation];
    } else {
        doesNotRecognizeSelector(cls, sel);
    }
}

void doesNotRecognizeSelector(Class cls, SEL sel) {
    if (class_respondsToSelector(cls, @selector(doesNotRecognizeSelector:))) {
        [cls doesNotRecognizeSelector:sel];
    } else {
        return nil;
    }
}

复制代码
  • 判断forwardingTargetForSelector快速转发,如果快速转发没有实现(返回nil)。
  • 判断是不是野指针,如果是野指针,则执行doesNotRecognizeSelector流程;否则执行methodSignatureForSelector慢速转发流程。
  • methodSignatureForSelector如果没有返回签名信息,不处理,也不会继续进行下面的流程。
  • 如果进入methodSignatureForSelector,先判断有没有实现_forwardStackInvocation,如果没有实现,进入_forwardStackInvocation流程;否则进入forwardInvocation流程。相当于_forwardStackInvocation是一个私有的前置条件。
  • 最后,如果forwardInvocation也没有实现,则执行doesNotRecognizeSelector流程。

消息转发流程图

第二次调用lookUpImpOrForward流程

上篇 动态方法决议 中,已经探索第二次执行lookUpImpOrForward的原因:是因为在消息转发流程中,调用了class_getInstanceMethod,该方法再次调用了lookUpImpOrForward,且 behavior = LOOKUP_RESOLVER,意思是在lookUpImpOrForward方法中再次执行动态方法决议。那么消息转发流程是如果调用class_getInstanceMethod的?

在执行第二次的resolveInstanceMethod中,打印sel

(lldb) po sel
"instanceMethod1"

(lldb) po sel
"instanceMethod1"

(lldb) bt
* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 1.1
  * frame #0: 0x000000010032acec libobjc.A.dylib`resolveInstanceMethod(inst=0x0000000000000000, sel="instanceMethod1", cls=ZLSubObject) at objc-runtime-new.mm:6274:21
    frame #1: 0x0000000100314423 libobjc.A.dylib`resolveMethod_locked(inst=0x0000000000000000, sel="instanceMethod1", cls=ZLSubObject, behavior=0) at objc-runtime-new.mm:6318:9
    frame #2: 0x00000001003130a6 libobjc.A.dylib`lookUpImpOrForward(inst=0x0000000000000000, sel="instanceMethod1", cls=ZLSubObject, behavior=0) at objc-runtime-new.mm:6582:16
    frame #3: 0x00000001002ea479 libobjc.A.dylib`class_getInstanceMethod(cls=ZLSubObject, sel="instanceMethod1") at objc-runtime-new.mm:6189:5
    frame #4: 0x00007fff20438653 CoreFoundation`__methodDescriptionForSelector + 276
    frame #5: 0x00007fff20451fa0 CoreFoundation`-[NSObject(NSObject) methodSignatureForSelector:] + 30
    frame #6: 0x00007fff204224ef CoreFoundation`___forwarding___ + 396
    frame #7: 0x00007fff204222d8 CoreFoundation`_CF_forwarding_prep_0 + 120
    frame #8: 0x0000000100003e10 ZLObjc`main(argc=<unavailable>, argv=<unavailable>) + 64 [opt]
    frame #9: 0x00007fff20362f5d libdyld.dylib`start + 1
    frame #10: 0x00007fff20362f5d libdyld.dylib`start + 1
(lldb)
复制代码

从堆栈信息中,可以看到: ___forwarding___methodSignatureForSelector__methodDescriptionForSelectorclass_getInstanceMethod

反汇编方式探究

  • CoreFoundation中,搜索methodSignatureForSelector

  • 查看__methodDescriptionForSelector
int ___methodDescriptionForSelector(int arg0, int arg1) {
    rbx = arg1;
    var_30 = *___stack_chk_guard;
    var_40 = arg0;
    // 如果obj为空,就是没有处理方法,跳转loc_120e62
    if (arg0 == 0x0) goto loc_120e62;
    
loc_120e62:
    // class_getInstanceMethod(cls, sel);
    rax = class_getInstanceMethod(var_40, rbx);
    if (rax != 0x0) {
        rax = method_getDescription(rax);
        r15 = *rax;
        rbx = *(rax + 0x8);
    } else {
        rbx = 0x0;
        r15 = 0x0;
    }
    goto loc_120e89;
    
loc_120e89:
    if (*___stack_chk_guard == var_30) {
        rax = r15;
    } else {
        rax = __stack_chk_fail();
    }
    // 返回 class_getInstanceMethod() 结果。
    return rax;
}
复制代码
  • 查看class_getInstanceMethod
Method class_getInstanceMethod {
    rax = _class_getInstanceMethod(rdi, rsi);
    return rax;
}
复制代码
  • 根据汇编特性,_class_getInstanceMethod其实是调用c++的class_getInstanceMethod函数:
Method class_getInstanceMethod(Class cls, SEL sel)
{
    if (!cls  ||  !sel) return nil;
    // 慢速查找方法,且 behavior = LOOKUP_RESOLVER
    lookUpImpOrForward(nil, sel, cls, LOOKUP_RESOLVER);
    // 查找 cls 的 method
    return _class_getMethod(cls, sel);
}
复制代码

符号断点探究

  • 根据堆栈信息,通过符号断点方式,添加methodSignatureForSelector:符号断点:

  • 进入__methodDescriptionForSelector

  • 进入class_getInstanceMethod

总结

  • 实例方法: - methodSignatureForSelector___methodDescriptionForSelectorclass_getInstanceMethodlookUpImpOrForwardresolveMethod_lockedresolveInstanceMethod
  • 类方法(元类对象方法): + methodSignatureForSelector___methodDescriptionForSelector(元类)class_getInstanceMethodlookUpImpOrForwardresolveMethod_lockedresolveClassMethod

消息发送所有流程总结

  • 消息发送的整体流程:消息缓存查找(快速查找)慢速查找动态方法决议消息转发(消息的快速转发、消息慢速转发)
  • 通过CacheLookup进行 消息快速查找。如果快速查找找不到imp,则进入消息慢速查找。
  • 通过lookUpImpOrForward进行 消息慢速查找。如果慢速也找不到imp,则进入动态方法决议。
  • 通过resolveMethod_locked进行 方法动态决议。如果动态方法决议没有返回imp,则进入消息转发流程。
  • 消息转发流程
    • 快速消息转发提供一个备用消息接收者,返回值不能为self,如果为nil则进入慢速消息转发。
    • 慢速消息转发需要提供消息签名,只要提供有效签名就可以解决消息发送错误问题。同时要实现forwardInvocation函数配合处理消息。
    • 在慢速消息转发后系统会再进行一次慢速消息查找流程。这次不会再进行消息转发。
    • 消息转发仍然没有解决问题,会进入doesNotRecognizeSelector,这个方法并不能处理错误,只能拿到错误信息。
© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享