msgSend底层(三)动态方法决议

415 阅读5分钟

经典案例

  • 定义一个方法, 本身类和父类都不去实现,就会爆出经典错误unrecognized selector sent to instance xxx,在msgSend底层(二)中,当父类为nil时,会进行一个赋值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,源码如下

image.png

动态方法决议

  • 慢速查找流程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 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);
}
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代码探究

@interface ZMPerson : NSObject
    - (void)sayHello;
    + (void)test;
@end
@implementation ZMPerson
+ (BOOL)resolveClassMethod:(SEL)sel {
    NSLog(@"--进入%@--",NSStringFromSelector(sel));
if (@selector(test) == sel) {
    IMP imp = class_getMethodImplementation(object_getClass([self class]), @selector(test2));
    Method mth = class_getClassMethod(object_getClass([self className]), @selector(test2));
    const char *type = method_getTypeEncoding(mth);
    return class_addMethod(object_getClass([self class]), sel, imp, type);
}
    return [super resolveClassMethod:sel];
}
+ (void)test2{
    NSLog(@"--%s---",__func__);
}

+(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
  • 整合动态方法决议 msg(cls, resolve_sel, sel) 也可以验证objc_msgSend发送消息不区分-+方法。objc_msgSend的接收者cls元类,意味着像元类中发消息,消息查找会到根元类去查找,所以 resolveInstanceMethod元类中,才会被调用,所以在类中的resolveInstanceMethod方法不会被调用,不是说元类的名字是一样的嘛,但是地址不一样哦

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

#import "NSObject+ZM.h"
#import <objc/runtime.h>
@implementation NSObject (ZM)
+(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

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

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