OC - 底层动态方法决议

281 阅读7分钟

前言

方法在日常开发中被我们广泛使用,但是我们从来不知道它的底层实现是怎么样的。今天我们将通过这篇文章一起来探索方法在底层中的实现,动态方法决议

实例查找

首先我们通过一个案例来进行分析。

@interface TPerson : NSObject

- (void)formatPerson;

@end

@implementation TPerson

@end
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        TPerson * tp = [TPerson alloc];
        [tp formatPerson];
    }
    return 0;
}

有一个对象 TPerson,在它里面声明了一个- (void)formatPerson方法,但是在.m文件中并没有实现。接下来我们运行代码。

-[TPerson formatPerson]: unrecognized selector sent to instance 0x101204b80
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[TPerson formatPerson]: unrecognized selector sent to instance 0x101204b80'
*** First throw call stack:
(
	0   CoreFoundation                      0x00007fff2049687b __exceptionPreprocess + 242
	1   libobjc.A.dylib                     0x00000001002fbd40 objc_exception_throw + 48
	2   CoreFoundation                      0x00007fff2051938d -[NSObject(NSObject) __retain_OA] + 0
	3   CoreFoundation                      0x00007fff203fe90b ___forwarding___ + 1448
	4   CoreFoundation                      0x00007fff203fe2d8 _CF_forwarding_prep_0 + 120
	5   KCObjcBuild                         0x0000000100003f20 main + 64
	6   libdyld.dylib                       0x00007fff2033ef5d start + 1
)
libc++abi: terminating with uncaught exception of type NSException
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[TPerson formatPerson]: unrecognized selector sent to instance 0x101204b80'
terminating with uncaught exception of type NSException

就出现了一个经典的unrecognized崩溃信息。通过前几篇文章我们知道了当消息进入慢速查找流程时,会走到核心方法lookUpImpOrForward中。

image.pnglookUpImpOrForward方法中我们可以得知,当找不到imp时,会被赋值成默认的(IMP)_objc_msgForward_impcache。接下来我们全局搜索_objc_msgForward_impcache

image.png 继续全局搜索__objc_msgForward

image.png 继续全局搜索__objc_forward_handler,发现并没有找到它的实现。我们尝试删掉一个_再试一试呢?

image.pngobjc-runtime.mm 目录下找到了实现。仔细一看这里不就是控制台打印的报错信息么。

动态方法决议

在慢速查找流程中,如果没有找到imp时,会走到动态方法决议流程中。

image.png

resolveMethod_locked

image.png 这个函数中有三个关键函数:1、resolveInstanceMethodresolveClassMethodlookUpImpOrForwardTryCache。接下来就对他们进行探究。

resolveInstanceMethod

static void resolveInstanceMethod(id inst, SEL sel, Class cls) // inst: 对象  cls: 类
{
    runtimeLock.assertUnlocked();
    ASSERT(cls->isRealized());
    SEL resolve_sel = @selector(resolveInstanceMethod:);
    // 检查 cls 的元类是否实现 resolveInstanceMethod 方法。如果没有实现也不会返回。因为系统默认实现了并且返回 NO。
    if (!lookUpImpOrNilTryCache(cls, resolve_sel, cls->ISA(/*authenticated*/true))) {
        // Resolver not implemented.
        return;
    }
    // 发送消息调用 resolveInstanceMethod 方法,通过 objc_msgSend 发送消息。接收者是 cls,说明是类方法。
    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
    // 查找 sel 对应的 imp,如果没有就把 forward_imp 赋值给 imp,并且插入缓存
    IMP imp = lookUpImpOrNilTryCache(inst, sel, cls);

    // resolved 和 imp 存在即说明动态添加了。
    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

static void resolveClassMethod(id inst, SEL sel, Class cls)   // inst: 对象  cls: 类
{
    runtimeLock.assertUnlocked();
    ASSERT(cls->isRealized());
    ASSERT(cls->isMetaClass());
    // 检查 cls 的元类是否实现 resolveClassMethod 方法。如果没有实现也不会返回。因为系统默认实现了
    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));
        }
    }
}

lookUpImpOrForwardTryCache

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

_lookUpImpTryCache

ALWAYS_INLINE
static IMP _lookUpImpTryCache(id inst, SEL sel, Class cls, int behavior)
{
    runtimeLock.assertUnlocked();
    // cls 类是否初始化
    if (slowpath(!cls->isInitialized())) {
        // see comment in lookUpImpOrForward
        // 如果没有初始化就在 lookUpImpOrForward 中查找
        return lookUpImpOrForward(inst, sel, cls, behavior);
    }
    // 缓存中查找 sel 对应的 imp
    IMP imp = cache_getImp(cls, sel);
    // imp 存在 走都 done
    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
    // imp 不存在,进入慢速查找流程
    if (slowpath(imp == NULL)) {
        return lookUpImpOrForward(inst, sel, cls, behavior);
    }

done:
    if ((behavior & LOOKUP_NIL) && imp == (IMP)_objc_msgForward_impcache) {
        return nil;
    }
    return imp;
}

代码探究 resolveInstanceMethod 和 resolveClassMethod

resolveInstanceMethod实例方法动态决议

添加如下代码

@interface TPerson : NSObject

- (void)formatPerson;

@end

@implementation TPerson

+ (BOOL)resolveInstanceMethod:(SEL)sel {
    NSLog(@" --- resolveInstanceMethod : %@ - %@",self, NSStringFromSelector(sel));
    return [super resolveInstanceMethod:sel];
}

@end
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        TPerson * tp = [TPerson alloc];
        [tp formatPerson];
    }
    return 0;
}

TPerson.m 中实现 + (BOOL)resolveInstanceMethod:(SEL)sel 方法,然后运行。

打印:

--- resolveInstanceMethod : TPerson - formatPerson
--- resolveInstanceMethod : TPerson - formatPerson
-[TPerson formatPerson]: unrecognized selector sent to instance 0x10072faa0
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[TPerson formatPerson]: unrecognized selector sent to instance 0x10072faa0'
*** First throw call stack:
(
	0   CoreFoundation                      0x00007fff2049687b __exceptionPreprocess + 242
	1   libobjc.A.dylib                     0x00000001002fbd40 objc_exception_throw + 48
	2   CoreFoundation                      0x00007fff2051938d -[NSObject(NSObject) __retain_OA] + 0
	3   CoreFoundation                      0x00007fff203fe90b ___forwarding___ + 1448
	4   CoreFoundation                      0x00007fff203fe2d8 _CF_forwarding_prep_0 + 120
	5   KCObjcBuild                         0x0000000100003e10 main + 64
	6   libdyld.dylib                       0x00007fff2033ef5d start + 1
)
libc++abi: terminating with uncaught exception of type NSException
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[TPerson formatPerson]: unrecognized selector sent to instance 0x10072faa0'
terminating with uncaught exception of type NSException
(lldb) 

发现在程序崩溃前调用了resolveInstanceMethod方法。但是为什么这里会打印两次呢?第一次是系统向resolveInstanceMethod发送消息,那第二次是什么时候调用的呢?后续我们进行分析。

我们如何让程序正常运行不崩溃呢?这里就讲到了动态添加方法。

#import "TPerson.h"
#import <objc/message.h>

@implementation TPerson

+ (BOOL)resolveInstanceMethod:(SEL)sel {
    NSLog(@" --- resolveInstanceMethod : %@ - %@",self, NSStringFromSelector(sel));
    if (sel == @selector(formatPerson)) {
        IMP imp = class_getMethodImplementation(self, @selector(getPerson));
        Method method = class_getInstanceMethod(self, @selector(getPerson));
        const char *type = method_getTypeEncoding(method);
        return class_addMethod(self, sel, imp, type);
    }
    return [super resolveInstanceMethod:sel];
}
@end
输出:
--- resolveInstanceMethod : TPerson - encodeWithOSLogCoder:options:maxLength:
<TPerson: 0x101551c00> - -[TPerson getPerson]

程序正常运行。resolveInstanceMethod 只调用了一次。因为动态添加了getPerson方法。在lookUpImpOrForwardTryCache获取调用 imp

流程:resolveMethod_locked -> resolveInstanceMethod -> resolveInstanceMethod -> lookUpImpOrNilTryCache(inst, sel, cls) -> lookUpImpOrForwardTryCache -> imp

resolveClassMethod类方法动态决议

添加如下代码

@interface TPerson : NSObject

+ (void)Person;

@end

@implementation TPerson

+ (BOOL)resolveClassMethod:(SEL)sel{
    NSLog(@"resolveClassMethod :%@-%@",self,NSStringFromSelector(sel));
    return [super resolveClassMethod:sel];
}


@end
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        [TPerson person];
    }
    return 0;
}
输出:
resolveClassMethod :TPerson-person
resolveClassMethod :TPerson-person
+[TPerson person]: unrecognized selector sent to class 0x100008208
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '+[TPerson person]: unrecognized selector sent to class 0x100008208'

在崩溃之前 resolveClassMethod 也调用了两次。

动态添加方法

+ (BOOL)resolveClassMethod:(SEL)sel{
    if (sel == @selector(person)) {
        IMP imp = class_getMethodImplementation(objc_getMetaClass("TPerson"), @selector(newPerson));
        Method method = class_getInstanceMethod(objc_getMetaClass("TPerson"), @selector(newPerson));
        const char *type = method_getTypeEncoding(method);
        return class_addMethod(objc_getMetaClass("TPerson"), sel, imp, type);
    }
    NSLog(@"resolveClassMethod :%@ - %@",self,NSStringFromSelector(sel));
    return [super resolveClassMethod:sel];
}

+ (void)newPerson {
    NSLog(@"%@ - %s",self , __func__);
}
输出:
TPerson - +[TPerson newPerson]

发现 resolveInstanceMethodresolveClassMethod 的流程基本一样。

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

#import "NSObject+TT.h"
#import <objc/message.h>

@implementation NSObject (TT)

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

+ (void)newPerson{
    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(formatPerson)) {
        IMP imp = class_getMethodImplementation(self, @selector(getPerson));
        Method method = class_getInstanceMethod(self, @selector(getPerson));
        const char *type = method_getTypeEncoding(method);
        return class_addMethod(self, sel, imp, type);
    } else  if (sel == @selector(person)) {
        IMP imp = class_getMethodImplementation(objc_getMetaClass("TPerson"), @selector(newPerson));
        Method method = class_getInstanceMethod(objc_getMetaClass("TPerson"), @selector(newPerson));
        const char *type = method_getTypeEncoding(method);
        return class_addMethod(objc_getMetaClass("TPerson"), sel, imp, type);
    }
    return NO;
}
@end

TPerson 中引入,然后运行程序。发现程序能够正常运行。

补充:oop 与 aop

  • oop:面向对象编程。什么人做什么事,分工明确。
    • 优点:低耦合
    • 缺点:冗余代码多,强耦合。
  • aop:面向切面编程。oop 的延伸。
    • 切点:切入的方法和类
    • 优点:通过动态方法进行方法的注入。
    • 缺点:性能消耗严重。