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

618 阅读15分钟

前言

方法在编程中占用重要的地位,大家对方法熟悉又陌生。熟悉的是每天都在用,陌生的是大家对底层的实现其实是一知半解的。前面探究了方法的快速查找流程和慢速查找流程,对方法底查找流程有一定的了解。如果快速查找流程和慢速查找流程都没有找打方法的实现,后面的流程是怎么样的,苹果会给一次机会动态方法决议

准备工作

案例分析

创建一个LWPerson类,声明一个sayHello方法,方法不实现

int main(int argc, char * argv[]) {
 
    @autoreleasepool {
        LWPerson * perosn  = [LWPerson alloc];
        [perosn sayHello];
 
    }
    return 0;
}

image.png

unrecognized经典的崩溃信息,在 IOS底层原理之方法慢速查找流程 中通过全局搜索doesNotRecognizeSelector或者unrecognized selector sent to instance,在源码中搜索方式查找到底层源码的实现,在方法查找流程中如果最后imp还是没有查找到,会调用forward_imp

image.png

forward_imp = _objc_msgForward_impcache,源码查看下 _objc_msgForward_impcache的底层实现,全局搜索_objc_msgForward_impcache

    STATIC_ENTRY __objc_msgForward_impcache

    // No stret specialization.
    b	__objc_msgForward  //跳转 __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

    END_ENTRY __objc_msgForward
    
    ...
    .macro TailCallFunctionPointer
	// $0 = function pointer value
	br	$0    //跳转 imp
    .endmacro
    ...
  • __objc_msgForward_impcache底层是汇编实现,主要代码 b __objc_msgForward
  • __objc_msgForwardTailCallFunctionPointer是个宏,前面探究过就是跳转impx17寄存器存放的是imp,从汇编中可以看出跟x17有关系的就是__objc_forward_handler
  • 全局搜索__objc_forward_handler 汇编中没有具体的实现,那就不在汇编中,可能在C/C++源码中,全局搜索objc_forward_handler,源码如下
// Default forward handler halts the process.
__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_fatal中报错熟悉不,经典的崩溃信息报错方法没有实现

在快速和慢速查找流程过程中没有找到imp,难道就直接崩溃,不给一次机会的嘛。不行必须给次机会,不然我不服气,系统还是干不过我哈,给了次机会就是动态方法决议

动态方法决议

在探究慢速查找流程lookUpImpOrForward中,如果没有查找到imp就会走动态方法决议流程resolveMethod_locked

NEVER_INLINE
IMP lookUpImpOrForward(id inst, SEL sel, Class cls, int behavior){
  ...
  
  // No implementation found. Try method resolver once.
  // 如果查询方法没有实现,系统会尝试一次方法解析
  if (slowpath(behavior & LOOKUP_RESOLVER)) {
      behavior ^= LOOKUP_RESOLVER;
      //动态方法决议
      return resolveMethod_locked(inst, sel, cls, behavior);
  }
 
  ...
}

下面看看resolveMethod_locked动态方法决议到底干了什么,源码如下

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

    runtimeLock.unlock();
    //判断cls类是否是元类,如果类说明说明调用的是实例方法
    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
    // 快速查找和慢速查找sel对应的imp返回imp 实际上就是从缓存中取,因为前面已经缓存过了
    return lookUpImpOrForwardTryCache(inst, sel, cls, behavior);
}
  • 首先判断cls是否是元类
  • 如果不是元类只是普通类,那么说明调用的实例方法跳转resolveInstanceMethod流程
  • 如果是元类,那么说明调用的是类方法跳转resolveClassMethod流程
  • lookUpImpOrForwardTryCache快速查找和慢速查找sel对应的imp 然后返回imp

resolveInstanceMethod方法

static void resolveInstanceMethod(id inst, SEL sel, Class cls)
{   // inst 对象  // cls 类
    runtimeLock.assertUnlocked();
    ASSERT(cls->isRealized());
    SEL resolve_sel = @selector(resolveInstanceMethod:);
    //只要cls的元类初始化 resolve_sel方法一定实现因为NSObject默认实现了resolveInstanceMethod
    //目的是将resolveInstanceMethod方法缓存到cls的元类中
    //通过lookUpImpOrNilTryCache的数我们知道`resolve_sel`是类方法
    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;
    //判断 resolve_sel 方法有没有实现,注意是`resolve_sel`方法
    bool resolved = msg(cls, resolve_sel, sel);

    
    //为什么还有调用 lookUpImpOrNilTryCache 查询缓存和慢速查找呢
    //虽然 resolveInstanceMethod 方法调用了。但是里面不一定实现了sel的方法
    // 所以还是要去查找sel对应的imp,如果没有实现就会把imp = forward_imp 插入缓存中
    // 以为慢速查找流程动态决议方法已经走过了,此时imp = forward_imp走down和down_unlock
    IMP imp = lookUpImpOrNilTryCache(inst, sel, cls);
     
    // resolved 和 imp 存在说明动态添加了
    if (resolved  &&  PrintResolving) {
        if (imp) {
            ...
        }
        else {
            // Method resolver didn't add anything?
            ...
        }
    }
}
  • 首先创建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_unlockdone流程,既selforward_imp插入缓存,进行消息转发

resolveInstanceMethod方法

static void resolveClassMethod(id inst, SEL sel, Class cls)
{
    runtimeLock.assertUnlocked();
    ASSERT(cls->isRealized());
    ASSERT(cls->isMetaClass());
    // inst 类 //cls元类
    //查询元类有没有实现  NSObject默认实现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);
    //类方法相当于元类中的实例方法,同样去快速和慢速的查找
    IMP imp = lookUpImpOrNilTryCache(inst, sel, cls);

    if (resolved  &&  PrintResolving) {
        if (imp) {
            ...
        }
        else {
            // Method resolver didn't add anything?
            ...
        }
    }
}
  • resolveClassMethodNSobject中已经实现,只要元类初始化就可以了,目的是缓存在元类中
  • 调用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)
{
    // LOOKUP_NIL = 4  没有传参数behavior = 0   0 | 4 = 4
    return _lookUpImpTryCache(inst, sel, cls, behavior | LOOKUP_NIL);
}

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

ALWAYS_INLINE
static IMP _lookUpImpTryCache(id inst, SEL sel, Class cls, int behavior)
{
    runtimeLock.assertUnlocked();
    //cls 是否初始化
    if (slowpath(!cls->isInitialized())) {
        // 没有初始化就去查找 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 进入慢速查找流程
    // behavior = 4 ,4 & 2 = 0 不会进入动态方法决议,所以不会一直循环
    if (slowpath(imp == NULL)) {
        return lookUpImpOrForward(inst, sel, cls, behavior);
    }

done:
    //(behavior & LOOKUP_NIL) = 4 & 4 = 1
    //LOOKUP_NIL 只是配合_objc_msgForward_impcache 写入缓存
    if ((behavior & LOOKUP_NIL) && imp == (IMP)_objc_msgForward_impcache) {
        return nil;
    }
    return imp;
}

判断cls是否初始化一般都会初始化的

缓存中查找

  • 在缓存中查找sel对应的imp
  • 如果imp存在跳转done流程
  • 判断是否有共享缓存给系统底层库用的
  • 如果缓存中没有查询到imp,进入慢速查找流程 慢速查找流程
  • 慢速查找流程中,behavior= 44 & 2 = 0进入动态方法决议,所以不会一直循环
  • 最重要的如果没有查询到此时imp= forward_imp,跳转lookUpImpOrForward中的done_unlockdone流程,插入缓存,返回forward_imp

done流程

  • done流程: (behavior & LOOKUP_NIL) 且 imp = _objc_msgForward_impcache,如果缓存中的是forward_imp,就直接返回nil,否者返回的impLOOKUP_NIL条件就是来查找是否动态添加了imp还有就是将imp插入缓存

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

resolveInstanceMethod实例探究

实现resolveInstanceMethod方法

LWPerson类中添加resolveInstanceMethod方法

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

+(BOOL)resolveInstanceMethod:(SEL)sel{
    NSLog(@"--进入%@--",NSStringFromSelector(sel));
    return [super resolveInstanceMethod:sel];
}
@end
2021-07-03 12:57:22.582805+0800 KCObjcBuild[7949:330351] --进入sayHello--
2021-07-03 12:57:22.583624+0800 KCObjcBuild[7949:330351] --进入sayHello--

在崩溃之前确实调用了resolveInstanceMethod方法

疑问:为什么会调用两次resolveInstanceMethod方法呢 第一次是走动态方法决议系统自动向resolveInstanceMethod发送消息,那么第二次是怎么调用的呢?

image.png

第一次调用resolveInstanceMethod的堆栈信息,可以看到走的是慢速查找流程的动态决议方法

image.png

第二次调用resolveInstanceMethod的堆栈信息,由底层系统库CoreFoundation调起,在消息转发完成以后再次开启慢速查找流程,进入动态方法决议又调用一次resolveInstanceMethod,所以总共是两次,第二次调用的详细流程在后面会作详细的阐述

动态添加sayHello方法

@implementation LWPerson

+(BOOL)resolveInstanceMethod:(SEL)sel{

    NSLog(@"--进入%@--",NSStringFromSelector(sel));
    if (@selector(sayHello) == sel) {
        IMP imp = class_getMethodImplementation(self , @selector(sayHello2));
        Method meth = class_getInstanceMethod(self , @selector(sayHello2));
        const char * type = method_getTypeEncoding(meth);
        return  class_addMethod(self ,sel, imp, type);;
    }
    return [super resolveInstanceMethod:sel];
}

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

@end
  • resolveInstanceMethod只调用一次,因为动态添加了sayHello方法lookUpImpOrForwardTryCache直接获取imp,直接调用imp,查找流程结束
  • 崩溃也解决了动态方法决议系统给了一次机会
  • 具体流程:resolveMethod_locked--> resolveInstanceMethod --> 调用resolveInstanceMethod --> lookUpImpOrNilTryCache(inst, sel, cls) --> lookUpImpOrForwardTryCache--> 调用imp

resolveClassMethod实例探究

实现resolveClassMethod方法

LWPerson类中添加resolveInstanceMethod方法,不动态实现test方法

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        LWPerson * person = [LWPerson alloc];
        [LWPerson test];  
    }
    return 0;
}
@implementation LWPerson
+(BOOL)resolveClassMethod:(SEL)sel{
    NSLog(@"--进入%@--",NSStringFromSelector(sel));
    return [super resolveClassMethod:sel];
}
@end
2021-07-03 13:51:22.784897+0800 KCObjcBuild[8483:359482] --进入test--
2021-07-03 13:51:22.785774+0800 KCObjcBuild[8483:359482] --进入test--
  • 在崩溃之前确实调用了resolveClassMethod方法,而且调用了两次,调用两次的逻辑和resolveInstanceMethod方法调用两次是一样的
  • 调用resolveClassMethod以后,会去查找lookUpImpOrNilTryCache有没有具体动态实现sel对应的imp,元类的缓存中此时有sel对应的imp,这个impforward_implookUpImpOrNilTryCache里面有判断直接返回nil,此时直接到resolveInstanceMethod查找,因为类方法实际上就是元类中的实例方法
  • 如果最后还是没有实现lookUpImpOrForwardTryCache获取到forward_imp进入消息转发流程

动态添加test方法

+(BOOL)resolveClassMethod:(SEL)sel{
  if (@selector(test) == sel) {
    NSLog(@"resolveClassMethod--进入%@--",NSStringFromSelector(sel));
    IMP imp = class_getMethodImplementation(object_getClass([self class]), @selector(newTest));
    Method meth = class_getClassMethod(object_getClass([self class]) , @selector(newTest));
    const char * type = method_getTypeEncoding(meth);
    return  class_addMethod(object_getClass([self class]) ,sel, imp, type);;
  }
    return [super resolveClassMethod:sel];
}

+(void)newTest{
    NSLog(@"--%s---",__func__);
}
2021-07-03 16:14:04.262688+0800 KCObjcBuild[9964:434208] resolveClassMethod--进入test--
2021-07-03 16:14:04.263331+0800 KCObjcBuild[9964:434208] --+[LWPerson newTest]---
  • resolveClassMethod只调用一次,因为动态添加了test方法
  • resolveClassMethodresolveInstanceMethod的调用流程基本一样,如果resolveClassMethod没有查询到调用一次resolveInstanceMethod调用

resolveClassMethod特殊之处

既然调用resolveClassMethod没有查询到,在调用接到resolveInstanceMethod查找,那么在LWPerson类中同时实现resolveClassMethodresolveInstanceMethod方法,理论上方法都能调用

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        LWPerson * person = [LWPerson alloc];
        [LWPerson test];  
    }
    return 0;
}
@implementation LWPerson
+(BOOL)resolveClassMethod:(SEL)sel{
    NSLog(@"resolveClassMethod--进入%@--",NSStringFromSelector(sel));
    return [super resolveClassMethod:sel];
}

+(BOOL)resolveInstanceMethod:(SEL)sel{

    NSLog(@"resolveInstanceMethod--进入%@--",NSStringFromSelector(sel));
    return [super resolveInstanceMethod:sel];
}
@end
2021-07-03 14:52:55.076902+0800 KCObjcBuild[9158:391254] resolveClassMethod--进入test--
2021-07-03 14:52:55.077890+0800 KCObjcBuild[9158:391254] resolveClassMethod--进入test--

结果和我们想的不一样,只调用LWPerson类中的resolveClassMethod,没有调用LWPersonresolveInstanceMethod方法,源码中不是显示了调用resolveInstanceMethod,那跟踪下源码

image.png instLWPerson类 ,clsLWPerson类的元类

image.png

lookUpImpOrNilTryCache的参数cls->ISA(true)是根源类,进入lookUpImpOrNilTryCache

image.png

此时instLWPerson类的元类cls是根源类,快速查找和慢速查找是到根元类查找,意味着元类调用了实例方法

image.png msg(cls, resolve_sel, sel) 也可以验证objc_msgSend发送消息不区分-+方法。objc_msgSend的接收者cls元类,意味着像元类中发消息,消息查找会到根元类去查找,所以 resolveInstanceMethod元类中,才会被调用,所以在类中的resolveInstanceMethod方法不会被调用,不是说元类的名字是一样的嘛,但是地址不一样哦

创建一个NSObject + LW,在分类中添加resolveInstanceMethod方法,因为根元类的父类是根类根元类找不到会到根类中查找,因为根元类没法创建所以只能用根类

@implementation NSObject (LW)
+(BOOL)resolveInstanceMethod:(SEL)sel{
    if (@selector(test) == sel) {
        NSLog(@"resolveInstanceMethod--进入%@--",NSStringFromSelector(sel));
    }
    return NO;
}
@end
2021-07-03 16:00:21.999137+0800 KCObjcBuild[9755:425282] resolveClassMethod--进入test--
2021-07-03 16:00:22.000222+0800 KCObjcBuild[9755:425282] resolveInstanceMethod--进入test--
2021-07-03 16:00:22.000579+0800 KCObjcBuild[9755:425282] resolveClassMethod--进入test--
2021-07-03 16:00:22.000681+0800 KCObjcBuild[9755:425282] resolveInstanceMethod--进入test--

跟源码分析的逻辑是一样的先调用resolveClassMethod,再调用resolveInstanceMethod,都是两次

整合动态方法决议

resolveClassMethod方法中如果没有动态添加类方法,会调用元类中的resolveInstanceMethod。那么能不能把resolveInstanceMethod写到一个公用类中,使类方法实例方法都能调用

  • 实例方法查找流程:对象 --> -->直到根类(NSObject) --> nil
  • 类方法查找流程: --> 元类 -->直到根类(NSObject) --> nil 到最后都找到NSObject类中,所以这个公用类就是NSObject分类
@implementation NSObject (LW)
+(BOOL)resolveInstanceMethod:(SEL)sel{
    if (@selector(sayHello) == sel) {
       NSLog(@"--进入%@--",NSStringFromSelector(sel));
       IMP imp = class_getMethodImplementation(self , @selector(sayHello2));
       Method meth = class_getInstanceMethod(self , @selector(sayHello2));
       const char * type = method_getTypeEncoding(meth);
       return  class_addMethod(self ,sel, imp, type);;
          
    }else if (@selector(test) == sel){
       NSLog(@"--进入%@--",NSStringFromSelector(sel));
       IMP imp = class_getMethodImplementation(object_getClass([self class]), @selector(newTest));
        Method meth = class_getClassMethod(object_getClass([self class]) , @selector(newTest));
       const char * type = method_getTypeEncoding(meth);
       return  class_addMethod(object_getClass([self class]) ,sel, imp, type);;
    }
    return NO;
}

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

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

@end
2021-07-03 16:54:56.295564+0800 KCObjcBuild[10394:453264] --进入sayHello--
2021-07-03 16:54:56.296146+0800 KCObjcBuild[10394:453264] ---[NSObject(LW) sayHello2]---
2021-07-03 16:54:56.296443+0800 KCObjcBuild[10394:453264] --进入test--
2021-07-03 16:54:56.296615+0800 KCObjcBuild[10394:453264] --+[NSObject(LW) newTest]---

实例方法是类方法调用,系统都自动调用了resolveInstanceMethod方法,和上面探究的吻合。 动态方法决议优点

  • 可以统一处理方法崩溃的问题,出现方法崩溃可以上报服务器,或者跳转到首页
  • 如果项目中是不同的模块你可以根据命名不同,进行业务的区别
  • 这种方式叫切面编程熟成AOP AOPOOP的区别
  • OOP:实际上是对对象的属性和行为的封装,功能相同的抽取出来单独封装,强依赖性,高耦合
  • AOP:是处理某个步骤和阶段的,从中进行切面的提取,有重复的操作行为,AOP就可以提取出来,运用动态代理,实现程序功能的统一维护,依赖性小,耦合度小,单独把AOP提取出来的功能移除也不会对主代码造成影响。AOP更像一个三维的纵轴,平面内的各个类有共同逻辑的通过AOP串联起来,本身平面内的各个类没有任何的关联

流程图后面补

消息转发

快速和慢速查找流程没有查询到,动态决议方法也没有查找到,下面就会进入消息转发流程,但是在objc4-818.2源码中没有发现相关的源码,CoreFunction提供的源码也不详细查询不到。苹果还是提供了日志辅助功能

日志辅助

通过lookUpImpOrForward --> log_and_fill_cache --> logMessageSend进入logMessageSend 看到源码的实现

if (slowpath(objcMsgLogEnabled && implementer)) {
        bool cacheIt = logMessageSend(implementer->isMetaClass(), 
                                      cls->nameForLogging(),
                                      implementer->nameForLogging(), 
                                      sel);
        if (!cacheIt) return;
    }

logMessageSend能调用objcMsgLogEnabled必须是YES

bool objcMsgLogEnabled = false;
static int objcMsgLogFD = -1;

bool logMessageSend(bool isClassMethod,
                    const char *objectsClass,
                    const char *implementingClass,
                    SEL selector)
{
    char	buf[ 1024 ];

    // Create/open the log file
    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;
        }
    }

    // Make the log entry
    snprintf(buf, sizeof(buf), "%c %s %s %s\n",
            isClassMethod ? '+' : '-',
            objectsClass,
            implementingClass,
            sel_getName(selector));

    objcMsgLogLock.lock();
    write (objcMsgLogFD, buf, strlen(buf));
    objcMsgLogLock.unlock();

    // Tell caller to not cache the method
    return false;
}

/tmp/msgSends是日志保存的沙盒路径,开启以后直接到沙盒路径下就能获取文件。默认的objcMsgLogEnabled = false 所以要找到赋值的地方

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;
}

通过instrumentObjcMessageSendsobjcMsgLogEnabled赋值,所以在需要日志信息的地方声明instrumentObjcMessageSendsextern void instrumentObjcMessageSends(BOOL flag);

extern void instrumentObjcMessageSends(BOOL flag);

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        LWPerson * p = [LWPerson alloc];
        instrumentObjcMessageSends(YES);
        [p sayHello];
        instrumentObjcMessageSends(NO);
      
    }
    return 0;
}

image.png 在动态决议方法之后的消息转发流程有forwardingTargetForSelectormethodSignatureForSelector后面会接着探讨消息转发流程

总结

动态方法决议相当于多给一次机会,给你动态实现的机会,同时也给了开发者更多新的尝试的机会。不得不说动态方法决议的流程是复杂的,想要搞的更加清楚明白需要细细的体会