阅读 96

9.ios-runtime 初探 _objc_msgSend 方法动态决议

当我们查找方法时缓存中没有,父类也没有时,则最后会给 imp = forwarding。const IMP forward_imp = (IMP)_objc_msgForward_impcache; 这个是什么则是这次探索的目标

错误方法的处理-消息处理

IMP lookUpImpOrForward(id inst, SEL sel, Class cls, int behavior)
{
...
const IMP forward_imp = (IMP)_objc_msgForward_impcache;

...
if (slowpath((curClass = curClass->getSuperclass()) == nil)) {
    // No implementation found, and method resolver didn't help.
    // Use forwarding.
    imp = forward_imp;
    break;
}
}
复制代码
  • _objc_msgForward_impcache 在源码中的位置,发现调用了 __objc_msgForward

image.png

image.png

  • __objc_forward_handler搜索不到则发现这个方法不是汇编,而搜索_objc_forward_handler 发现这个方法调用了 objc_defaultForwardHandler
  • 返回值 objc_defaultForwardHandler 上边的错误报错打印

image.png

进入保错处理前给了一次机会就在 resolveMethod_locked

对象方法动态决议

  • 查看宏定义枚举值

584361F80A4DC9E23A201A2B2D785661.jpg

  • 进入这个方法判断中

2ABA92CEBFF2C361265872DD8253E81C.jpg

  • 查看behavior 等于3

image.png

  • 结果为2大于0 则进入方法

74FAF730064D970151AD37885E93B025.jpg

  • 进入后改变了behavior的值为1,再次判断的话则为0 不在进入,所以得出结论这个判断只会走一次。是一个单例。

123BB1AEF9EC8846593A0D73119079AE.jpg

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

    runtimeLock.unlock();
    // 程序员 你是不是傻 没有这个方法 - imp-nil
    // 奔溃 - 友善
    // 给你一次机会 拯救地球 -> imp
    //判断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);  //由于类在元类中其实也是对象,最终调用父类会进入 NSObject 中,所以要再次调用对象方法的这个处理实现方法中。
        }
    }
    // 快速查找和慢速查找sel对应的imp返回imp 实际上就是从缓存中取,因为前面已经缓存过了
    // chances are that calling the resolver have populated the cache
    // so attempt using it
    return lookUpImpOrForwardTryCache(inst, sel, cls, behavior);
}



复制代码

对象方法进入 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));
        }
    }
}

复制代码
  • 1.SEL resolve_sel = @selector(resolveInstanceMethod:); 获取这个方法保存到变量中
  • 2. BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend; bool resolved = msg(cls, resolve_sel, sel);系统主动调用 resolveInstanceMethod方法,并返回BOOL
  • 3.我理解这个是 相当于 objc_msgSend(cls @selector(resolveInstanceMethod:@selector(xxx)))
  • 4.为什么不是对象 inst 而是 cls 元类呢,元类调用 "-(void)"对象方法,相当于 对象调用了 +(void)类方法。这就是为什么 resolveInstanceMethod 在类中写的是类方法。
  • 5.当我们重写了 resolveInstanceMethod方法时,这个方法会在系统内存加载的时候存放入方法列表中,开始,objc_msgSend 这个就是走的消息发送流程,会快速查找缓存,后再慢速查找方法列表。既然已经重写了这个方法必然是能查找到执行的。
  • 6.执行之后把一个未实现的方法的方法名和一个已经实现的方法地址绑定一起放入到方法列表中。这就是这次处理错误的结果。

重写 resolveInstanceMethod方法 处理

+ (BOOL)resolveInstanceMethod:(SEL)sel{
    // resolveInstanceMethod :LGTeacher-say666 为什么是两次 家庭作业
    // 处理 sel -> imp
    if (sel == @selector(say666)) {
        IMP sayNBImp     = class_getMethodImplementation(self, @selector(sayNB));
        Method method    = class_getInstanceMethod(self, @selector(sayNB));
        const char *type = method_getTypeEncoding(method);
        return class_addMethod(self, sel, sayNBImp, type);
    }
    
    NSLog(@"resolveInstanceMethod :%@-%@",self,NSStringFromSelector(sel));

    return [super resolveInstanceMethod:sel];
}
复制代码

image.png

  • 若无重写则调用系统的方法,默认返回NO。

image.png

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 元类的以对象方法的方法

// 元类的以对象方法的方法
+ (BOOL)resolveClassMethod:(SEL)sel{
   
    NSLog(@"resolveClassMethod :%@-%@",self,NSStringFromSelector(sel));
    
    if (sel == @selector(sayHappy)) {
        IMP sayNBImp     = class_getMethodImplementation(objc_getMetaClass("LGTeacher"), @selector(sayKC));
        Method method    = class_getInstanceMethod(objc_getMetaClass("LGTeacher"), @selector(sayKC));
        const char *type = method_getTypeEncoding(method);
        return class_addMethod(objc_getMetaClass("LGTeacher"), sel, sayNBImp, type);
    }

    return [super resolveClassMethod:sel];
}

复制代码

你也可以统一处理,创建分类来处理,来处理所有的类的动态决议

image.png

#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN

@interface NSObject (LG)

@end

NS_ASSUME_NONNULL_END


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


@implementation NSObject (LG)

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

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

#pragma clang diagnostic push
// 让编译器忽略错误
#pragma clang diagnostic ignored "-Wundeclared-selector"


+ (BOOL)resolveInstanceMethod:(SEL)sel{
    // resolveInstanceMethod :LGTeacher-say666 为什么是两次 家庭作业
    // 处理 sel -> imp
    
    NSLog(@"resolveInstanceMethod :%@-%@",self,NSStringFromSelector(sel));

    if (sel == @selector(say666)) {
        IMP sayNBImp     = class_getMethodImplementation(self, @selector(sayNB));
        Method method    = class_getInstanceMethod(self, @selector(sayNB));
        const char *type = method_getTypeEncoding(method);
        return class_addMethod(self, sel, sayNBImp, type);
    }else  if (sel == @selector(sayHappy)) {
        IMP sayNBImp     = class_getMethodImplementation(objc_getMetaClass("LGTeacher"), @selector(sayKC));
        Method method    = class_getInstanceMethod(objc_getMetaClass("LGTeacher"), @selector(sayKC));
        const char *type = method_getTypeEncoding(method);
        return class_addMethod(objc_getMetaClass("LGTeacher"), sel, sayNBImp, type);
    }
    return NO;
}
@end

复制代码

最后当处理完成动态决议后调用 lookUpImpOrNilTryCache

IMP lookUpImpOrNilTryCache(id inst, SEL sel, Class cls, int behavior)
{
    return _lookUpImpTryCache(inst, sel, cls, behavior | LOOKUP_NIL);
}
_lookUpImpTryCache 这里面也是快速查找流程和慢速查找流程。如果你写了 resolveInstanceMethod 这个方法并正确添加了就能找到方法地址了。
复制代码
  • 有imp就走这里了打印了一段信息

image.png

  • 最后这个

image.png resolveInstanceMethod(inst, sel, cls);先进入这个方法处理完成后,调用 lookUpImpOrForwardTryCache 这个方法其实就是调用了_lookUpImpTryCache 这个其实就是消息发送的机制流程,先查找缓存方法,再调用lookUpImpOrForward方法。

//#pragma clang diagnostic pop

/**

say666 () sel -> imp

1: 苹果给的一次机会
2: 全局 所有方法找不到 我们是不是都能监听
3: lg_model_traffic(项目) -> lg_home_didClickDetail (模块) -> pop home(处理) -> 发送消息didClickDetail (发送后台) -> 监控 改BUG
4: runtime ->
5: aop - oop 漫谈iOS AOP编程之路
6: 对象分工是非常明确 - 冗余代码 -> 提取 -> 公共的类 (强依赖 - 强耦合)
7: 无侵入 - 动态 注入代码 - 切入的方法 切入的类
8: 性能消耗 + 苹果写转发流程就没有意义
9: 消息转发流程 : 快速 + 慢速转发

*/

  • 1.由于可能写了一个不存在的方法报错奔溃,对用户的体验不好。系统给了一次机会来处理
  • 2.如果方法无法找到就必然会走一次 resolveInstanceMethod
  • 3.利用aop编程对错误处理发送日志记录奔溃信息
文章分类
iOS
文章标签