iOS底层-15:动态方法决议与消息转发

动态方法决议与转发

动态方法决议

接着前面我们讲到了resolveMethod_locked

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

    runtimeLock.unlock();
    //判断是否是元类,元类用resolveClassMethod处理;非元类resolveInstanceMethod处理
    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 (!lookUpImpOrNil(inst, sel, cls)) {
            resolveInstanceMethod(inst, sel, cls);
        }
    }

    // chances are that calling the resolver have populated the cache
    // so attempt using it
    return lookUpImpOrForward(inst, sel, cls, behavior | LOOKUP_CACHE);
}

判断当前类是否为元类,元类用resolveClassMethod处理;非元类resolveInstanceMethod处理。处理完之后调用lookUpImpOrForward继续查找imp。

我们先看实例方法,

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

    //判断元类里是否有resolveInstanceMethod方法,没有直接return
    if (!lookUpImpOrNil(cls, resolve_sel, cls->ISA())) {
        // 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 = lookUpImpOrNil(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));
        }
    }
}
  1. lookUpImpOrNil判断cls的元类有没有实现resolveInstanceMethod,若没有实现,直接return。
  2. objc_msgSendcls发送一个resolveInstanceMethod消息
    3.再继续查找imp,如果上resolveInstanceMethod方法给imp做了处理,那么这次就可以查到imp了。

LRPerson类里面重写resolveInstanceMethod方法

+ (BOOL)resolveInstanceMethod:(SEL)sel {
    NSLog(@"%s 来了",__func__);
    return [super resolveInstanceMethod:sel];
}

运行代码打印结果为:

+[LRPerson resolveInstanceMethod:] 来了
+[LRPerson resolveInstanceMethod:] 来了
-[LRPerson sayHello]: unrecognized selector sent to instance 0x1011277a0
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[LRPerson sayHello]: unrecognized selector sent to instance 0x1011277a0'
*** First throw call stack:
(
    0   CoreFoundation                      0x00007fff2e63fd07 __exceptionPreprocess + 250
    1   libobjc.A.dylib                     0x000000010038e04a objc_exception_throw + 42
    2   CoreFoundation                      0x00007fff2e6bec97 -[NSObject(NSObject) __retain_OA] + 0
    3   CoreFoundation                      0x00007fff2e5a457b ___forwarding___ + 1427
    4   CoreFoundation                      0x00007fff2e5a3f58 _CF_forwarding_prep_0 + 120
    5   KCObjc                              0x0000000100000e60 main + 64
    6   libdyld.dylib                       0x00007fff68371cc9 start + 1
    7   ???                                 0x0000000000000001 0x0 + 1
)
libc++abi.dylib: terminating with uncaught exception of type NSException
(lldb) 

上述打印结果,调用了两次resolveInstanceMethod方法。我们通过断点一步步调试

1.`[person sayHello]`进入`lookUpImpOrForward`开始慢速查找
inst:  
sel = "sayHello" cls = LRPerson  behavior = 3

2.在类的继承链中查找`imp`,通过 `getMethodNoSuper_nolock`方法

3.没有找到`imp`,赋值`imp=forward_imp`,跳出循环进入`if (slowpath(behavior & LOOKUP_RESOLVER))`
behavior ^= LOOKUP_RESOLVER = 1
进入动态方法决议

4.进入`resolveMethod_locked`
inst:  
sel = "sayHello" cls = LRPerson  behavior = 1

5.LRPerson不是元类,走resolveInstanceMethod方法

6.`resolveInstanceMethod`方法
inst:  
sel = "sayHello" cls = LRPerson 

7.`objc_msgSend`调用一次类方法`resolveInstanceMethod`  //打印第一次+[LRPerson resolveInstanceMethod:] 来了

8.`lookUpImpOrNil`查找一次`imp`,看是否在resolveInstanceMethod中做了处理
lookUpImpOrNil直接调用lookUpImpOrForward(obj, sel, cls, behavior | LOOKUP_CACHE | LOOKUP_NIL);

9.进入`lookUpImpOrForward`,参数发生了改变
inst:  
sel = "sayHello" cls = LRPerson  behavior = 12

10. behavior = 12 会通过cache_getImp取一次缓存,取不到
然后再循环查找`getMethodNoSuper_nolock`,还是没有找到

11. behavior = 12 不满足条件,不会再执行`resolveMethod_locked`,会直接跳到`log_and_fill_cache`将`forward_imp`存进了缓存

12. behavior满足条件直接返回`nil`
if (slowpath((behavior & LOOKUP_NIL) && imp == forward_imp)) {
        return nil;
   }

13.回到`resolveInstanceMethod`,`imp`为`nil`,回到上一层`resolveMethod_locked`,调用`lookUpImpOrForward(inst, sel, cls, behavior | LOOKUP_CACHE);`

14.再一次进入`lookUpImpOrForward`,此时的参数为
inst:  
sel = "sayHello" cls = LRPerson  behavior = 5

15.再一次`cache_getImp`,拿到了11步,存入的`forward_imp`,直接goto done_nolock,返回了`imp`

//forwardingTargetForSelector
16.之后会调用LRPerson的`forwardingTargetForSelector:`

17.进入`lookUpImpOrForward`
inst: <0x0> 
sel = "forwardingTargetForSelector:" cls = LRPerson  behavior = 14

18.behavior & LOOKUP_CACHE 满足条件,`cache_getImp`,因为没有实现`forwardingTargetForSelector`,`imp`找不到。

19.`getMethodNoSuper_nolock`循环查找父类,在NSObject类中找到实现。
存入缓存,返回`imp`。

//methodSignatureForSelector
20.之后会调用LRPerson的`methodSignatureForSelector:`

21.进入`lookUpImpOrForward`
inst: <0x0> 
sel = "methodSignatureForSelector:" cls = LRPerson  behavior = 14

22.behavior & LOOKUP_CACHE 满足条件,`cache_getImp`,因为没有实现`methodSignatureForSelector`,`imp`找不到。

23.`getMethodNoSuper_nolock`循环查找父类,在NSObject类中找到实现。
存入缓存,返回`imp`。

//class 
24.重复上面的流程`getMethodNoSuper_nolock`循环查找父类,在NSObject类中找到实现。
存入缓存,返回`imp`。

//sayHello
25.之后会调用LRPerson的`sayHello`
inst: <0x0> 
sel = "sayHello" cls = LRPerson  behavior = 2

26. `getMethodNoSuper_nolock`循环查找父类,没有找到。

27.满足`behavior & LOOKUP_RESOLVER`条件,又一次进入`resolveMethod_locked`
behavior = 0

28.`resolveInstanceMethod` -> objc_msgSend 

29.又调用了一次`resolveInstanceMethod`  //打印第二次+[LRPerson resolveInstanceMethod:] 来了

30. `lookUpImpOrNil`->lookUpImpOrForward

31.进入`lookUpImpOrForward`,此时的参数为
inst: <0x0> 
sel = "sayHello" cls = LRPerson  behavior = 12

32.`cache_getImp`找不到;`getMethodNoSuper_nolock`循环查找也找不到;
将imp=_objc_msgForward_impcache 存入缓存并返回nil

33.回到`resolveMethod_locked`方法,调用`lookUpImpOrForward(inst, sel, cls, behavior | LOOKUP_CACHE);`

34.进入`lookUpImpOrForward`,此时的参数为
inst: <0x0> 
sel = "sayHello" cls = LRPerson  behavior = 4

35.`cache_getImp`,拿到`imp`,并`return imp`

//doesNotRecognizeSelector
36.之后会调用LRPerson的`doesNotRecognizeSelector:`

37.进入`lookUpImpOrForward`
inst: <0x0> 
sel = "doesNotRecognizeSelector:" cls = LRPerson  behavior = 14

38.behavior & LOOKUP_CACHE 满足条件,`cache_getImp`,因为没有实现`doesNotRecognizeSelector`,`imp`找不到。

39.`getMethodNoSuper_nolock`循环查找父类,在NSObject类中找到实现。
存入缓存,返回`imp`。
.
.
.

  • 第一次是消息转发之前再给一次机会,调用一次resolveInstanceMethod
  • 第二次是转发之后,methodSignatureForSelector调用之后,forwardInvocation之前,转发的类调用sayHello也会查找imp,也会调用一次resolveInstanceMethod

通过打印堆栈也可以清晰的查看这个流程,在图上位置打上断点:


  • 第一次进来打印堆栈情况


  • 第二次堆栈情况


类方法

当进来的方法是类方法的时候,走的是下面的流程


大致与实例方法一样,但是为什么在调用了resolveClassMethod之后,出来还要走一次resolveInstanceMethod

先看resolveClassMethod源码

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

    //判断元类cls是否有resolveClassMethod
    if (!lookUpImpOrNil(inst, @selector(resolveClassMethod:), cls)) {
        // Resolver not implemented.
        return;
    }
    //nonmeta经过处理得到的是LRPerson
    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);
        }
    }
  //向当前的类发送resolveClassMethod方法
    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 = lookUpImpOrNil(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));
        }
    }
}

resolveInstanceMethod基本一致,只是给一次机会的时候,改为调用类的resolveClassMethod,然后继续在元类里查找imp

  • 为什么元类方法会走resolveInstanceMethod
    类方法其实就是元类的Instance方法,我们虽然不能重写元类的代码,但是不要忘了元类的继承链。元类-> 根元类 -> NSObject,我们可以在NSObject中重写resolveInstanceMethod

类方法的拯救机会可以写在两个地方:
1.类的resolveClassMethod
2.NSObject的resolveInstanceMethod

resolveInstanceMethod的写法举个例子:

+ (BOOL)resolveInstanceMethod:(SEL)sel {
    NSLog(@"%s 来了",__func__);
    if (sel == @selector(sayHello)) {
       

        IMP imp           = class_getMethodImplementation(self, @selector(say1));
        Method sayMMethod = class_getInstanceMethod(self, @selector(say1));
        const char *type  = method_getTypeEncoding(sayMMethod);
        return class_addMethod(self, sel, imp, type);
    }
    return [super resolveInstanceMethod:sel];
}

+ (BOOL)resolveClassMethod:(SEL)sel {
    NSLog(@"%s 来了",__func__);
    if (sel == @selector(sayClassMethod)) {
        
        IMP imp           = class_getMethodImplementation(objc_getMetaClass("LRPerson"), @selector(say8));
        Method sayMMethod = class_getInstanceMethod(objc_getMetaClass("LRPerson"), @selector(say8));
        const char *type  = method_getTypeEncoding(sayMMethod);
        return class_addMethod(objc_getMetaClass("LRPerson"), sel, imp, type);
    }
    return [super resolveClassMethod:sel];
}

消息转发

instrumentObjcMessageSends函数作用是监控objc底层消息的发送

//1.objcMsgLogEnabled 控制开关
//2.外部使用 extern
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;
}

修改源码运行:

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // insert code here...
       
        LRPerson * person = [LRPerson alloc];
        instrumentObjcMessageSends(YES);
        [person  sayHello];
        instrumentObjcMessageSends(NO);    
    }
    return 0;
}

会生成log日志,保存在/tmp/msgSends-%d路径下

resolveInstanceMethod之后会调用一下方法:

  • forwardingTargetForSelector
  • methodSignatureForSelector
    .
    .
  • doesNotRecognizeSelector
    我们分析一下这些方法到底有什么作用。
forwardingTargetForSelector 快速转发

查看官方文档command+shift+0,搜索forwardingTargetForSelector

返回一个unrecognized messages第一个指向的对象

代码验证:

//新建一个LRStudent继承于LRPerson,在子类中实现sayHello方法
@interface LRStudent : LRPerson

@end
@implementation LRStudent
- (void)sayHello {
    NSLog(@"LRStudent sayHello");
}

@end


//在LRPerson.m中重写![![未命名文件.png](https://upload-images.jianshu.io/upload_images/23932582-19d755722b2ca9fe.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
](https://upload-images.jianshu.io/upload_images/23932582-a124cc493c3a00e0.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
方法,返回一个LRStudent对象
@implementation LRPerson
- (id)forwardingTargetForSelector:(SEL)aSelector {
    NSLog(@"%@",NSStringFromSelector(aSelector));
    return [LRStudent alloc];
}
@end

打印结果:

sayHello
LRStudent sayHello
Program ended with exit code: 0

成功的将父类的方法调用子类的实现。

methodSignatureForSelector 慢速转发


返回selectorNSMethodSignature方法签名,当动态方法决议和快速转发都没有处理的时候,会走到这个方法。

此方法一般搭配forwardInvocation:,他们一起实现了消息的慢速转发。在LRPerson类中添加以下方法,运行发现没有实现sayHello也不会崩溃了。

- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
    NSMethodSignature * m =[NSMethodSignature signatureWithObjCTypes:"v16@0:8"];
    return m;
}
- (void)forwardInvocation:(NSInvocation *)anInvocation {
    
//    [anInvocation invoke];
    
}

我们可以修改anInvocationtargetsel,我们在这里可以改变消息的接受者,也可以改变调用的方法。

拓展

调用[person sayHello];,不做任何处理。崩溃后通过lldb调试。

  • bt查看崩溃时的堆栈信息(从下往上看)

  • image list当前所有镜像文件

  • 前往当前文件夹找到CoreFoundation

  • CoreFoundation拖入Hopper

  • command + f全局搜索__forwarding_prep_0___

  • 点击状态栏按钮Pseudo-code mode查看伪代码

  • 向下查找


    很容易找到forwardingTargetForSelector:,继续往下还可以methodSignatureForSelector:_forwardStackInvocation:,整个调用流程清晰可见。借助反汇编工具Hopper,更好探索方法调用过程。

你可能感兴趣的:(iOS底层-15:动态方法决议与消息转发)