IOS底层原理之动态方法决议

前言

IOS底层原理之方法慢速查找流程 中我们对方法的慢速查找流程进行了分析,如果方法最终找不到时,会将imp赋值为forward_imp然后返回,返回后有发生了什么呢,今天来继续探索:

一、找不到方法实现,底层原理

代码实验

上代码:

@interface MHPerson1 : NSObject
-(void)testFunc;

@end
@implementation MHPerson1
@end


int main(int argc, const char * argv[]) {
    @autoreleasepool {
        MHPerson1 *person = [MHPerson1 alloc];
        [person testFunc];
        
    }
    return 0;
}

报错信息如下

*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[MHPerson1 testFunc]: unrecognized selector sent to instance 0x10060dd20'
*** First throw call stack:
(
	0   CoreFoundation                      0x00007fff2070587b __exceptionPreprocess + 242
	1   libobjc.A.dylib                     0x00000001002fbb20 objc_exception_throw + 48
	2   CoreFoundation                      0x00007fff2078838d -[NSObject(NSObject) __retain_OA] + 0
	3   CoreFoundation                      0x00007fff2066d90b ___forwarding___ + 1448
	4   CoreFoundation                      0x00007fff2066d2d8 _CF_forwarding_prep_0 + 120
	5   KCObjcBuild                         0x0000000100003a20 main + 64
	6   libdyld.dylib                       0x00007fff205adf5d start + 1
	7   ???                                 0x0000000000000001 0x0 + 1
)
libc++abi: terminating with uncaught exception of type NSException

查询报错

搜一下,找到了这个,还用 class_isMetaClass(object_getClass(self)) ? ‘+’ : ‘-’,对类方法和实例方法做了判断

__attribute__((noreturn, cold)) 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;

再搜一下_objc_forward_handler,找到了这个

STATIC_ENTRY __objc_msgForward_impcache

	// No stret specialization.
	b	__objc_msgForward

	END_ENTRY __objc_msgForward_impcache

	
	ENTRY __objc_msgForward

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

__objc_msgForward_impcache,这个似曾相识,之前的IOS底层原理之方法慢速查找流程 中,我们知道查找方法的方法是lookUpImpOrForward,里面有个这个
const IMP forward_imp = (IMP)_objc_msgForward_impcache;
连上了,然后看汇编代码 发现调用了 TailCallFunctionPointer x17这个,搜一下

.macro TailCallFunctionPointer
	// $0 = function pointer value
	braaz	$0
.endmacro

准备工作完成,下面进入动态方法决议

二、动态方法决议


 if (slowpath(behavior & LOOKUP_RESOLVER)) {
     behavior ^= LOOKUP_RESOLVER;
     // 动态决议方法
     return resolveMethod_locked(inst, sel, cls, behavior);
 }
 enum {
    LOOKUP_INITIALIZE = 1,
    LOOKUP_RESOLVER = 2,
    LOOKUP_NIL = 4,
    LOOKUP_NOCACHE = 8,
};
	// lookUpImpOrForward(obj, sel, cls, LOOKUP_INITIALIZE | LOOKUP_RESOLVER)
	// receiver and selector already in x0 and x1
	mov	x2, x16
	mov	x3, #3

第一次时,behavior = 3 、LOOKUP_RESOLVER = 2,3&2 = 2,所以if表达式成立。
那么接下来执行的方法就是resolveMethod_locked

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()) {
        // try [cls resolveInstanceMethod:sel]
        resolveInstanceMethod(inst, sel, cls);
    } 
    else {
        // try [nonMetaClass resolveClassMethod:sel]
        // and [cls resolveInstanceMethod:sel]
        resolveClassMethod(inst, sel, cls);
        if (!lookUpImpOrNilTryCache(inst, sel, cls)) {
            resolveInstanceMethod(inst, sel, cls);
        }
    }

    // chances are that calling the resolver have populated the cache
    // so attempt using it
    return lookUpImpOrForwardTryCache(inst, sel, cls, behavior);
}
  • 首先判断cls是否是元类
  • 如果不是元类只是普通类,那么说明调用的实例方法跳转resolveInstanceMethod流程
  • 如果是元类,那么说明调用的是类方法跳转resolveClassMethod流程
  • lookUpImpOrForwardTryCache快速查找和慢速查找sel对应的imp 然后返回imp

看一下这个resolveInstanceMethod

resolveInstanceMethod

static void resolveInstanceMethod(id inst, SEL sel, Class cls)
{
    runtimeLock.assertUnlocked();
    ASSERT(cls->isRealized());
    SEL resolve_sel = @selector(resolveInstanceMethod:);

    if (!lookUpImpOrNilTryCache(cls, resolve_sel, cls->ISA(/*authenticated*/true))) {
        // 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 = 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));
        }
    }
}
  • 首先创建resolveInstanceMethod 的SEL resolve_sel
  • 根据lookUpImpOrNilTryCache (cls, resolve_sel, cls->ISA(true))知道resolveInstanceMethod是类方法,通过快速和慢速查找流程查找resolve_sel对应的imp,缓存resolveInstanceMethod方法
  • 直接通过msg(cls, resolve_sel, sel)给类发送消息,从这里也能看到resolveInstanceMethod是类方法

lookUpImpOrNilTryCache(inst, sel, cls)快速和慢速查找流程

  • 通过lookUpImpOrNilTryCache来确定resolveInstanceMethod方法中有没有实现sel对应的imp
  • 如果实现了,缓存中没有,进入lookUpImpOrForward查找到sel对应imp插入缓存,调用imp查找流程结束
  • 如果没有实现,缓存中没有,进入lookUpImpOrForward查找,sel没有查找到对应的imp,此时imp = forward_imp,动态方法决议只调用一次,此时会走done_unlock和done流程,既sel和forward_imp插入缓存,进行消息转发

resolveClassMethod

static void resolveClassMethod(id inst, SEL sel, Class cls)
{
    runtimeLock.assertUnlocked();
    ASSERT(cls->isRealized());
    ASSERT(cls->isMetaClass());

    if (!lookUpImpOrNilTryCache(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 = 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在NSobject中已经实现,只要元类初始化就可以了,目的是缓存在元类中
  • 调用resolveClassMethod类方法,目的是实现可能resolveClassMethod``方法中动态实现sel对应的imp
  • imp = lookUpImpOrNilTryCache(inst, sel, cls) 缓存sel对应的imp,不管imp有没有动态添加,如果没有缓存的就是forward_imp

lookUpImpOrNilTryCache方法

lookUpImpOrNilTryCache方法名字,可以理解就是查找imp或者nil尽可能的通过查询cache的方式,在resolveInstanceMethod方法和resolveClassMethod方法都调用lookUpImpOrNilTryCache extern IMP lookUpImpOrNilTryCache(id obj, SEL, Class cls, int behavior = 0);

IMP lookUpImpOrNilTryCache(id inst, SEL sel, Class cls, int behavior)
{
    return _lookUpImpTryCache(inst, sel, cls, behavior | LOOKUP_NIL);
}

首先最后一个参数默认是behavior = 0,LOOKUP_NIL = 4, behavior|LOOKUP_NIL 大于等于LOOKUP_NIL ALWAYS_INLINE

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

    if (slowpath(!cls->isInitialized())) {
        // see comment in lookUpImpOrForward
        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:
    if ((behavior & LOOKUP_NIL) && imp == (IMP)_objc_msgForward_impcache) {
        return nil;
    }
    return imp;
}

判断cls是否初始化一般都会初始化的缓存中查找

  • 在缓存中查找sel对应的imp
  • 如果imp存在跳转done流程
  • 判断是否有共享缓存给系统底层库用的
  • 如果缓存中没有查询到imp,进入慢速查找流程
慢速查找流程
慢速查找流程中,behavior= 4 ,4 & 2 = 0进入动态方法决议,所以不会一直循环
最重要的如果没有查询到此时imp= forward_imp,跳转lookUpImpOrForward中的done_unlock和done流程,插入缓存,返回forward_imp
done流程
done流程: (behavior & LOOKUP_NIL) 且 imp = _objc_msgForward_impcache,如果缓存中的是forward_imp,就直接返回nil,否者返回的imp,LOOKUP_NIL条件就是来查找是否动态添加了imp还有就是将imp插入缓存

lookUpImpOrNilTryCache的主要作用通过LOOKUP_NIL来控制插入缓存,不管sel对应的imp有没有实现,还有就是如果imp返回了有值那么一定是在动态方法决议中动态实现了imp

容错处理

@implementation MHPerson1

- (void)undefinedFun
{
    NSLog(@"undefinedFun");
}
    
+ (BOOL)resolveInstanceMethod:(SEL)sel
{
    if (sel == @selector(testFunc)) {
        IMP imp = class_getMethodImplementation(self, @selector(undefinedFun));
        Method method = class_getInstanceMethod(self, @selector(undefinedFun));
        const char *type = method_getTypeEncoding(method);
        class_addMethod(self, sel, imp, type);
    }
    return NO;
}

@end

三、aop和oop

oop

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

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

aop

面向切面编程,是oop的延伸

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

总结:

大部分都是代码,需要一定的耐心来操作验证,希望越来越好

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值