010-消息的动态转发流程(上)

通过这篇文章可以获得什么:

探索消息转发的起因

  • 我在FFPerson类里面创建了一个实例方法likeGirls,但是并未实现
  • 然后我在main.m文件中初始化FFPerson类,并调用likeGirls方法
  • 出现经典错误:'-[FFPerson likeGirls]: unrecognized selector sent to instance 0x1006106e0',无法找到该方法的实例,即未实现

案例代码

@interface FFPerson : NSObject
//声明了一个实例方法
- (void)likeGirls;
@end

@implementation FFPerson
//该实例方法并没有实现
@end


int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // insert code here...
        
        //初始化FFPerson的实例对象
        FFPerson *person = [FFPerson alloc];
        //调用likeGirls实例方法
        [person likeGirls];
    }
    return 0;
}
复制代码

控制台经典报错:

2021-07-04 09:58:10.002598+0800 001-探索消息转发的起因[12184:1009168] -[FFPerson likeGirls]: unrecognized selector sent to instance 0x1006106e0
2021-07-04 09:58:10.017465+0800 001-探索消息转发的起因[12184:1009168] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[FFPerson likeGirls]: unrecognized selector sent to instance 0x1006106e0'
*** First throw call stack:
(
	0   CoreFoundation                      0x00007fff205a56af __exceptionPreprocess + 242
	1   libobjc.A.dylib                     0x00007fff202dd3c9 objc_exception_throw + 48
	2   CoreFoundation                      0x00007fff20627c85 -[NSObject(NSObject) __retain_OA] + 0
	3   CoreFoundation                      0x00007fff2050d07d ___forwarding___ + 1467
	4   CoreFoundation                      0x00007fff2050ca38 _CF_forwarding_prep_0 + 120
	
	6   libdyld.dylib                       0x00007fff2044e621 start + 1
)
libc++abi.dylib: terminating with uncaught exception of type NSException
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[FFPerson likeGirls]: unrecognized selector sent to instance 0x1006106e0'
terminating with uncaught exception of type NSException
(lldb) 
复制代码

截图补充:
消息转发的起因-实例方法未实现.png
疑问点一:当方法未实现的时候为什么会报unrecognized selector sent to instance这个错误呢?

通过前几次文章的分享,知道了一个消息的查找流程:

  1. 快速查找流程(已完成探索)->
  2. 慢速查找流程(已完成探索)->
  3. 动态方法转发流程(本篇探索内容)

当消息进入到慢速查找流程的时候最终会停留在LookUpImpOrForward这个核心方法上,那么我此篇内容依然接续着慢速查找流程无法匹配的情况看LookUpImpOrForword的后续操作,所以入手点就在这个方法,全局搜索:
查找LookUpImpOrForward.png

objc4源码解答(截取关键源码):

IMP lookUpImpOrForward(id inst, SEL sel, Class cls, int behavior)
{
    const IMP forward_imp = (IMP)_objc_msgForward_impcache;
    IMP imp = nil;

    for (unsigned attempts = unreasonableClassCount();;) {

        if (slowpath((curClass = curClass->getSuperclass()) == nil)) {
            //当消息的快速查找、慢速查找流程都走完还找不到的情况下
            //imp会给一个默认值forward_imp,进入消息转发流程
            imp = forward_imp;
            break;
        }
    }
    return imp;
}
复制代码

通过LookUpImpOrForward这个方法可以得知,imp在未找到的时候会被默认赋值一个forward_imp,forward_imp只是一个符号,它代表的是(IMP)_objc_msgForward_impcache,接下来去全局搜索(IMP)_objc_msgForward_impcache

_objc_msgForward_impcache.png

继续全局查找__objc_msgForward,我这里还是只关心arm64的架构:

__objc_msgForward.png

全局搜索__objc_forward_handler,发现没有我想要的结果,没有找到具体的实现地方

__objc_forward_handler.png

__objc_forward_handler的下划线“_”删除一个,再次搜索_objc_forward_handler

_objc_forward_handler.png

到这里,守得云开见月明,看到了控制台打印为什么会提示unrecognized selector sent to instance?

探索流程图:

控制台输出信息查找流程图.png

总结:

  1. 由控制台打印信息unrecognized selector sent to instance为出发点,据悉向下深挖方法的响应流程

  2. 通过对LookUpImpOrForward的分析得出在慢速查找未找到的时候,给imp默认赋值forward_imp,即objc_msgForward_impcache

  3. 通过对objc_msgForward_impcache的查找,发现在汇编中它只是一个中间函数,真正指向的是objc_msgForward

  4. objc_msgForward的分析得出,方法内调用了objc_forward_handle获取返回值存到x17寄存器,再由TailCallFunctionPointer方法跳转到x17寄存器内真正指向的imp地址

  5. _objc_forward_handler会给定默认的实现objc_defaultForwardHandle,当imp最终的查找流程全部走完的时候,还未找到imp,那么此时就会进入objc_defaultForwardHandle函数,将错误信息打印出来。

动态方法决议

当上述一系列的方法都走完之后,无法找到sel对应的imp,这个是后循环查找结束了,接下来进入动态方法决议

objc4源码解答(截取关键源码):

IMP lookUpImpOrForward(id inst, SEL sel, Class cls, int behavior)
{
    const IMP forward_imp = (IMP)_objc_msgForward_impcache;
    IMP imp = nil;

    for (unsigned attempts = unreasonableClassCount();;) {

        if (slowpath((curClass = curClass->getSuperclass()) == nil)) {
            //当消息的快速查找、慢速查找流程都走完还找不到的情况下
            //imp会给一个默认值forward_imp,进入消息转发流程
            imp = forward_imp;
            break;
        }
        //找到了sel对应的imp
        if (fastpath(imp)) {
            goto done;
        }
    }
    /**
    * 如果遍历查找的过程找到了,会跳过此步骤,取到done分支,进行后续操作
    * 如果找不到,会进行下面这个算法,最终进入resolveMethod_locked函数
    * 此算法真正达到的目的为单例,保证一个lookUpImpOrForward
    * 只执行一次resolveMethod_locked
    */ 
    if (slowpath(behavior & LOOKUP_RESOLVER)) {
        behavior ^= LOOKUP_RESOLVER;
        return resolveMethod_locked(inst, sel, cls, behavior);
    }
    return imp;
}
复制代码

位运算实现单例解读

操作符号解读:

  • &:按位与,比较的是二进制,相同位置数字相同为1,不同为0
  • ^:按位或,比较的是二进制,相同位置数字不同为1,相同为0
  • ^=:按位或,比较的是二进制,相同位置数字不同为1,相同为0,将结果赋值为运算符左边。

前提条件(初始值)

behavior = 3
LOOKUP_RESOLVER = 2

初次判断操作

操作一:behavior & LOOKUP_RESOLVER = 3 & 2 = 0x11 & 0x10 = 0x10 = 2

重置behaivor

操作二:behavior = behavior ^ LOOKUP_RESOLVER = 3 ^ 2 = 0x11 ^ 0x10 = 0x01 = 1

再次进入判断操作(第二次至无限次)

操作三:behavior & LOOKUP_RESOLVER = 1 & 2 = 0x01 & 0x10 = 0x00 = 0

结论

保证了每一个lookUpImpOrForward函数最多只能执行一次resolveMethod_locked(动态方法决议),直到behavior重新赋值

resolveMethod_locked

此方法存在的意义:当你调用了一个方法的时候,第一进入消息的快速查询流程 -> 然后进入消息的慢速查找流程,当底层源码已经给你方法查找了2遍之后依然找不到你实现的地方,此时imp=nil,理论上来讲程序应该崩溃,但是在开发者的角度上来讲,此做法会令这个框架不稳定,或者说这个系统很不友善。所以此框架决定再给你一次机会,让你重新拯救地球,为你提供了一个自定义的imp返回的机会,resolveMethod_locked此函数就是动态消息转发的入口

static NEVER_INLINE IMP
resolveMethod_locked(id inst, SEL sel, Class cls, int behavior)
{
    runtimeLock.assertLocked();
    ASSERT(cls->isRealized());

    runtimeLock.unlock();

    if (! cls->isMetaClass()) {
        //imp为实例方法
        resolveInstanceMethod(inst, sel, cls);
    } 
    else {
        //imp为类方法
        resolveClassMethod(inst, sel, cls);
        if (!lookUpImpOrNilTryCache(inst, sel, cls)) {
            resolveInstanceMethod(inst, sel, cls);
        }
    }

    // 经过resolveInstanceMethod函数很有可能已经对sel对应imp完成了动态添加
    // 所以再一次尝试查找
    return lookUpImpOrForwardTryCache(inst, sel, cls, behavior);
}
复制代码

此函数里面有三个关键的函数:

  • resolveInstanceMethod:实例方法动态添加imp

  • resolveClassMethod:类方法动态添加imp

  • lookUpImpOrForwardTryCache,当完成添加之后,回到之前的慢速查找流程再来一遍

resolveInstanceMethod

/*********************************************************
* 解析实例方法
* 调用+resolveInstanceMethod,寻找要添加到类cls的方法。
* cls 可能是元类或非元类。
* 不检查该方法是否已经存在。
*********************************************************/
static void resolveInstanceMethod(id inst, SEL sel, Class cls)
{
    runtimeLock.assertUnlocked();
    ASSERT(cls->isRealized());
    SEL resolve_sel = @selector(resolveInstanceMethod:);

    //当你为实现resolveInstanceMethod的时候,此处也不会进入return
    //因为系统给resolveInstanceMethod函数默认返回NO,即默认实现了
    if (!lookUpImpOrNilTryCache(cls, resolve_sel, cls->ISA(/*authenticated*/true))) {
        return;
    }

    //系统会在此处为你发送一个消息resolve_sel
    //当你的这个类检测了这个消息,并且做了处理
    BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
    bool resolved = msg(cls, resolve_sel, sel);

    //那么此时系统会重新查找,此函数最终会触发LookUpImpOrForward
    IMP imp = lookUpImpOrNilTryCache(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));
        }
    }
}
复制代码

resolveClassMethod

/*********************************************************
* 解析类方法
* 调用+resolveClass 方法,寻找要添加到类cls 的方法。
* cls 应该是一个元类。
* 不检查该方法是否已经存在。
*********************************************************/
static void resolveClassMethod(id inst, SEL sel, Class cls)
{
    runtimeLock.assertUnlocked();
    ASSERT(cls->isRealized());
    ASSERT(cls->isMetaClass());

    //当你为实现resolveClassMethod的时候,此处也不会进入return
    //因为系统给resolveClassMethod函数默认返回NO,即默认实现了
    if (!lookUpImpOrNilTryCache(inst, @selector(resolveClassMethod:), cls)) {
        // Resolver not implemented.
        return;
    }
    
    //nonmeta容错处理
    Class nonmeta;
    {
        mutex_locker_t lock(runtimeLock);
        nonmeta = getMaybeUnrealizedNonMetaClass(cls, inst);
        if (!nonmeta->isRealized()) {
            _objc_fatal("nonmeta class %s (%p) unexpectedly not realized",
                        nonmeta->nameForLogging(), nonmeta);
        }
    }
    //系统会在此处为你发送一个消息resolveClassMethod
    //当你的这个类检测了这个消息,并且做了处理
    BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
    bool resolved = msg(nonmeta, @selector(resolveClassMethod:), sel);

    //那么此时系统会重新查找,此函数最终会触发LookUpImpOrForward
    IMP imp = lookUpImpOrNilTryCache(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));
        }
    }
}
复制代码

resolveClassMethod & resolveInstanceMethod

//默认返回NO,当用户不实现这个方法的时候,程序也不会return
+ (BOOL)resolveClassMethod:(SEL)sel {
    return NO;
}
//默认返回NO,当用户不实现这个方法的时候,程序也不会return
+ (BOOL)resolveInstanceMethod:(SEL)sel {
    return NO;
}
复制代码

lookUpImpOrForwardTryCache

过度函数

IMP lookUpImpOrForwardTryCache(id inst, SEL sel, Class cls, int behavior)
{
    return _lookUpImpTryCache(inst, sel, cls, behavior);
}
复制代码

_lookUpImpTryCache

通过动态添加方法之后,再次尝试查找sel对应的最新添加的imp

ALWAYS_INLINE
static IMP _lookUpImpTryCache(id inst, SEL sel, Class cls, int behavior)
{
    runtimeLock.assertUnlocked();

    //当类未初始化的时候,进入lookUpImpOrForward
    //在里面处理不缓存任何方法
    if (slowpath(!cls->isInitialized())) {
        return lookUpImpOrForward(inst, sel, cls, behavior);
    }
    
    //通过汇编查找
    IMP imp = cache_getImp(cls, sel);
    if (imp != NULL) goto done;
    //共享缓存查找
#if CONFIG_USE_PREOPT_CACHES
    if (fastpath(cls->cache.isConstantOptimizedCache(/* strict */true))) {
        imp = cache_getImp(cls->cache.preoptFallbackClass(), sel);
    }
#endif
    //如果依然找不到,证明方法真的不存在,也就是说,只能依靠方法的动态添加了
    //那么此时再次进入方法的慢速查找流程
    if (slowpath(imp == NULL)) {
        return lookUpImpOrForward(inst, sel, cls, behavior);
    }

done:
    //此判断是当前imp已经存在了,并且这个imp是默认赋值的forward_imp
    //此时返回imp为nil;
    if ((behavior & LOOKUP_NIL) && imp == (IMP)_objc_msgForward_impcache) {
        return nil;
    }
    return imp;
}
复制代码

疑问点二:resolveMethod_locked与resolveInstanceMethod函数都会执行lookUpImpOrNilTryCache,为什么要执行2遍呢?

实例方法的动态决议

阶段一:按照源码还原

通过源码已得知会给开发者一次弥补的机会,通过resolveInstanceMethod这个方法,这里在程序内模拟一下这个方法;

@implementation FFPerson
+ (BOOL)resolveInstanceMethod:(SEL)sel {
    NSLog(@"手动介入resolveInstanceMethod :%@-%@",self,NSStringFromSelector(sel));
    return [super resolveInstanceMethod:sel];
}
@end
复制代码

控制台打印结果:

021-07-04 16:29:18.779380+0800 001-探索消息转发的起因[14290:1164992] 手动介入resolveInstanceMethod :FFPerson-likeGirls
2021-07-04 16:29:18.779920+0800 001-探索消息转发的起因[14290:1164992] 手动介入resolveInstanceMethod :FFPerson-likeGirls
2021-07-04 16:29:18.780020+0800 001-探索消息转发的起因[14290:1164992] -[FFPerson likeGirls]: unrecognized selector sent to instance 0x100606150
2021-07-04 16:29:18.780574+0800 001-探索消息转发的起因[14290:1164992] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[FFPerson likeGirls]: unrecognized selector sent to instance 0x100606150'
复制代码

依然报错是在意料之中的事情,因为此时方法还并未实现,但是在崩溃之前打印了我手动介入的信息,也就是说,在崩溃之前有补救的办法。

阶段二:实现\动态添加IMP

案例代码

@implementation FFPerson

//创建一个IMP
- (void)prettyGirls {
    NSLog(@"%s",__func__);
}

+ (BOOL)resolveInstanceMethod:(SEL)sel {
    if (sel == @selector(likeGirls)) {
        //当sel==likeGirls,动态给这个sel指定一个新的imp(prettyGirls)
        IMP prettyGirlsImp = class_getMethodImplementation(self, @selector(prettyGirls));
        Method method = class_getInstanceMethod(self, @selector(prettyGirls));
        const char * type = method_getTypeEncoding(method);
        return  class_addMethod(self, sel, prettyGirlsImp, type);
    }
    NSLog(@"手动介入resolveInstanceMethod :%@-%@",self,NSStringFromSelector(sel));
    return [super resolveInstanceMethod:sel];
}
@end
复制代码

控制台打印结果:

2021-07-04 16:46:17.907703+0800 001-探索消息转发的起因[14403:1174269] -[FFPerson prettyGirls]
复制代码

类方法的动态决议

案例代码

@interface FFPerson : NSObject
//声明了一个实例方法
+ (void)enjoyLife;
@end

@implementation FFPerson
//该类方法并没有实现
@end

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // insert code here...
        
        //初始化FFPerson的实例对象
        FFPerson *person = [FFPerson alloc];
        //调用未实现的类方法
        [FFPerson enjoyLife];
    }
    return 0;
}
复制代码

控制台经典报错

2021-07-04 17:15:18.903974+0800 001-探索消息转发的起因[14552:1188609] +[FFPerson enjoyLife]: unrecognized selector sent to class 0x100008188
2021-07-04 17:15:18.904697+0800 001-探索消息转发的起因[14552:1188609] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '+[FFPerson enjoyLife]: unrecognized selector sent to class 0x100008188'
*** First throw call stack:
(
	0   CoreFoundation                      0x00007fff205a56af __exceptionPreprocess + 242
	1   libobjc.A.dylib                     0x00007fff202dd3c9 objc_exception_throw + 48
	2   CoreFoundation                      0x00007fff20627bdd __CFExceptionProem + 0
	3   CoreFoundation                      0x00007fff2050d07d ___forwarding___ + 1467
	4   CoreFoundation                      0x00007fff2050ca38 _CF_forwarding_prep_0 + 120
	
	6   libdyld.dylib                       0x00007fff2044e621 start + 1
)
libc++abi.dylib: terminating with uncaught exception of type NSException
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '+[FFPerson enjoyLife]: unrecognized selector sent to class 0x100008188'
terminating with uncaught exception of type NSException
(lldb) 
复制代码

阶段一:按照源码还原

通过源码已得知会给开发者一次弥补的机会,通过resolveInstanceMethod这个方法,这里在程序内模拟一下这个方法;

@implementation FFPerson
+ (BOOL)resolveInstanceMethod:(SEL)sel {
    NSLog(@"手动介入resolveInstanceMethod :%@-%@",self,NSStringFromSelector(sel));
    return [super resolveInstanceMethod:sel];
}
@end
复制代码

控制台打印

2021-07-04 17:19:48.778680+0800 001-探索消息转发的起因[14583:1191738] 手动介入resolveInstanceMethod :FFPerson-_dynamicContextEvaluation:patternString:
2021-07-04 17:19:48.791081+0800 001-探索消息转发的起因[14583:1191738] 手动介入resolveInstanceMethod :FFPerson-enjoyLife
2021-07-04 17:19:48.791255+0800 001-探索消息转发的起因[14583:1191738] +[FFPerson enjoyLife]: unrecognized selector sent to class 0x1000081a8
复制代码

依然报错是在意料之中的事情,因为此时方法还并未实现,但是在崩溃之前打印了我手动介入的信息,也就是说,在崩溃之前有补救的办法。

阶段二:实现\动态添加IMP

案例代码

@implementation FFPerson

//为元类对象创建创建一个IMP
+ (void)enjoyTime {
    NSLog(@"%@ - %s",self,__func__);
}

+ (BOOL)resolveClassMethod:(SEL)sel {
    
    if (sel == @selector(enjoyLife)) {
        
        IMP enjoyTimeImp = class_getMethodImplementation(objc_getMetaClass("FFPerson"), @selector(enjoyTime));
        Method method = class_getInstanceMethod(objc_getMetaClass("FFPerson"), @selector(enjoyTime));
        const char * type = method_getTypeEncoding(method);
        return  class_addMethod(objc_getMetaClass("FFPerson"), sel, enjoyTimeImp, type);
    }
    NSLog(@"手动介入resolveInstanceMethod :%@-%@",self,NSStringFromSelector(sel));
    
    return [super resolveClassMethod:sel];
}
复制代码

控制台打印结果:

2021-07-04 17:40:30.032916+0800 001-探索消息转发的起因[14750:1203053] FFPerson - +[FFPerson enjoyTime]
复制代码

结论:

  1. 通过手动添加resolveInstanceMethod/resolveClassMethod,先打印验证在崩溃前有没有开发者的操作空间

  2. 通过判断当前查找的sel动态添加一个新的imp

  3. 经过论证,此方法是可以的。此时sel(likeGirls/enjoyLife)对应实现的imp不再是likeGirls/enjoyLife,而是变成了我动态指定的prettyGirls/enjoyTime

疑问点三:类动态方法决议会进入resolveClassMethod,然后根据判断有可能会再次进入resolveInstanceMethod,为什么?

整合实例对象/类对象的动态方法决议

回到现实,日常开发中不可能每个类中都实现一份resolveInstanceMethod/resolveClassMethod,这是比较真实的场景,那么这里通过创建NSObject分类的方式将此实现都放在分类中:

NSObject+FF

@implementation NSObject (FF)

//为实例对象创建一个IMP
- (void)prettyGirls {
    NSLog(@"%s",__func__);
}

//为元类对象创建创建一个IMP
+ (void)enjoyTime {
    NSLog(@"%@ - %s",self,__func__);
}

#pragma clang diagnostic push
// 让编译器忽略错误
#pragma clang diagnostic ignored "-Wundeclared-selector"

+ (BOOL)resolveInstanceMethod:(SEL)sel {
    
    NSLog(@"手动介入resolveInstanceMethod :%@-%@",self,NSStringFromSelector(sel));
    
    if (sel == @selector(likeGirls)) {
        
        IMP prettyGirlsImp = class_getMethodImplementation(self, @selector(prettyGirls));
        Method method = class_getInstanceMethod(self, @selector(prettyGirls));
        const char * type = method_getTypeEncoding(method);
        return  class_addMethod(self, sel, prettyGirlsImp, type);
    } else if (sel == @selector(enjoyLife)) {
        
        IMP enjoyTimeImp = class_getMethodImplementation(objc_getMetaClass("FFPerson"), @selector(enjoyTime));
        Method method = class_getInstanceMethod(objc_getMetaClass("FFPerson"), @selector(enjoyTime));
        const char * type = method_getTypeEncoding(method);
        return  class_addMethod(objc_getMetaClass("FFPerson"), sel, enjoyTimeImp, type);
    }

    return NO;
}
#pragma clang diagnostic pop
@end
复制代码

main.m

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // insert code here...
        
        FFPerson *person = [FFPerson alloc];
        //调用未实现的实例方法
        [person likeGirls];
        //调用未实现的类方法
        [FFPerson enjoyLife];
        
    }
    return 0;
}
复制代码

打印结果(部分关键打印)

2021-07-04 18:06:55.491847+0800 001-探索消息转发的起因[14926:1216030] 手动介入resolveInstanceMethod :FFPerson-likeGirls
2021-07-04 18:06:55.491952+0800 001-探索消息转发的起因[14926:1216030] -[NSObject(FF) prettyGirls]
2021-07-04 18:06:55.492192+0800 001-探索消息转发的起因[14926:1216030] 手动介入resolveInstanceMethod :FFPerson-enjoyLife
2021-07-04 18:06:55.502342+0800 001-探索消息转发的起因[14926:1216030] FFPerson - +[NSObject(FF) enjoyTime]
复制代码

完整的Demo

疑问点四:动态方法决议的作用是什么?Apple为什么这么去设计?

oop与aop

  • oop:面向对象编程,什么人做什么什么事情,分工非常明确

    • 好处:耦合度很低
    • 痛点:有很多冗余代码,常规解决办法是提取,那么会有一个公共的类,所有人对公共的类进行集成,那么所有人对公共类进行强依赖,也就代表着出现了强耦合
  • aop:面向切面编程,是oop的延伸

    • 切点:要切入的方法和切入的,比如上述的例子中的enjoyLifeFFPerson
    • 优点:对业务无侵入,通过动态方式将某些方法进行注入
    • 缺点:做了一些判断,执行了很多无关代码,包括系统方法,造成性能消耗。会打断apple的方法动态转发流程。

解答在源码探索过程中提出的疑问点:

当方法未实现的时候为什么会报unrecognized selector sent to instance这个错误呢?

通过上述的论证可以知道当方法未实现的时候,进入方法慢速查找之后,依然找不到,会给方法一个默认值_objc_msgForward_impcache,此默认值通过一系列的查找,最终会找到objc_defaultForwardHandle函数,此函数里面标注了明确的错误信息

resolveMethod_locked与resolveInstanceMethod函数都会执行lookUpImpOrNilTryCache,为什么要执行2遍呢?

通过调试发现了原因是多执行了respondsToSelector等方法,具体原因未探索清楚,后续补充

类动态方法决议会进入resolveClassMethod,然后根据判断有可能会再次进入resolveInstanceMethod,为什么?

正常的类对象动态方法决议会进入resolveClassMethod,这点是毋庸置疑的,但是类方法查找过程是在元类中查找,那么通过isa的指向图中可以得知,类方法的查找过程也是有继承关系的,会一直向上找,找到superMetaClass,找到rootMetaClass,最终找到NSObject,到这一层,所有的方法对于NSObject来讲都是实例方法所以会调用resolveInstanceMethod

动态方法决议的作用是什么?Apple为什么这么去设计?

首先这是苹果给开发者最后一次机会,让我们自行处理一下你找不到的方法。将resolveInstanceMethod方法写在NSNbject分类中,意味着全局所有方法找不到,我们都可以监听的到。创建一个NSObject分类“项目名_模块_事物”,比如,FF_Home_didClickDetail,此didClickDetail事件未实现,那么第一时间向后台报告,哪个项目哪个模块,哪个时间导致了crash,让你极致的拯救你的KPI,第二件事就是防止carsh,当时间未实现的时候,跳转到Home

感悟:

通过此过程的探索,想要成为一个高逼格的开发人员,一定要给别人更多的容错处理。

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享