序言

iOS 全网最新objc4 可调式/编译源码
编译好的源码的下载地址

iOS底层之Runtime探究(一)
iOS底层之Runtime探究(二)
前面的文章中讲到了objc_msgSend的办法查找进程,在通过lookUpImpOrForward办法的慢速查找没有找到imp后,就到了动态办法抉择流程resolveMethod_locked

动态办法抉择

static NEVER_INLINE IMP
resolveMethod_locked(id inst, SEL sel, Class cls, int behavior)
{
    runtimeLock.assertLocked();
    ASSERT(cls->isRealized());
    runtimeLock.unlock();
    // 元类判别
    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
    return lookUpImpOrForwardTryCache(inst, sel, cls, behavior);
}
  • 判别当时的class是否为元类
  • 不是元类,走实力办法流程resolveInstanceMethod
  • 元类,走类办法流程resolveClassMethod,判别resolveClassMethod是否已处理问题,未处理再走resolveInstanceMethod流程;
  • 走完动态抉择流程,重新找一次imp

resolveInstanceMethod办法解析

static void resolveInstanceMethod(id inst, SEL sel, Class cls)
{
    runtimeLock.assertUnlocked();
    ASSERT(cls->isRealized());
    SEL resolve_sel = @selector(resolveInstanceMethod:);
    // 1.查询元类是否完结了resolveInstanceMethod:办法
    if (!lookUpImpOrNilTryCache(cls, resolve_sel, cls->ISA(/*authenticated*/true))) {
        // Resolver not implemented.
        return;
    }
    // 2.发送音讯resolveInstanceMethod:
    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
    // 3.再次查询sel对应的imp,resolveInstanceMethod可动态修正
    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. 界说resolveInstanceMethod:办法;
  2. lookUpImpOrNilTryCache(cls, resolve_sel, cls->ISA(true)):查询元类是否完结了resolveInstanceMethod:办法,cls->ISA(true)为类isa指向的元类,假如未完结直接回来;
  3. 通过objc_msgSend发送resolveInstanceMethod:音讯,将回来成果给resolved
  4. lookUpImpOrNilTryCache(inst, sel, cls):再次查询当时类cls是否完结了sel对应的imp

lookUpImpOrNilTryCache办法解析

image.png

  • lookUpImpOrNilTryCache:是直接调用了_lookUpImpTryCachebehavior值为LOOKUP_NIL
  • _lookUpImpTryCache:的核心功用是再次走一次快速慢速办法查询。behavior值为LOOKUP_NIL,所以即便再次查不到也不会再走resolveMethod_locked流程。

resolveClassMethod办法解析

static void resolveClassMethod(id inst, SEL sel, Class cls)
{
    runtimeLock.assertUnlocked();
    ASSERT(cls->isRealized());
    ASSERT(cls->isMetaClass());
    // 1.检查元类是否完结办法resolveClassMethod:
    if (!lookUpImpOrNilTryCache(inst, @selector(resolveClassMethod:), cls)) {
        // Resolver not implemented.
        return;
    }
    // 2.通过cls,获取处理的nonmeta
    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);
        }
    }
    // 3.发送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
    // 4.再次查询imp是否完结
    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));
        }
    }
}
  1. 界说resolveClassMethod:办法;
  2. 通过办法lookUpImpOrNilTryCache,检查元类是否完结办法resolveClassMethod:,假如未完结直接回来;
  3. 通过cls,获取处理的nonmeta
  4. 通过objc_msgSend发送resolveClassMethod:音讯,将回来成果给resolved
  5. lookUpImpOrNilTryCache(inst, sel, cls):再次查询当时元类cls是否完结了sel对应的imp

动态办法抉择流程图

动态办法抉择-导出(1).png

动态抉择实例

界说一个类LGTeacher,声明两个办法,不在m文件完结。

  • 实例办法sayHelllo
  • 类办法sayByeBye

完结办法resolveInstanceMethod:但不做处理

image.png

实例办法

调用实例办法sayHello办法运转

image.png
LGTeacher中的resolveInstanceMethod:办法成功进来,而且进来了两次,然后翻开resolveInstanceMethod:中的注释代码运转。

image.png
运转成功,咱们成功通过动态向类增加办法lg_instanceMethod处理了sayHello办法找不到,引起的溃散问题。

类办法

调用类办法sayByeBye办法运转

image.png
LGTeacher中的resolveClassMethod:办法成功进来,也是进来了两次,然后翻开resolveClassMethod:中的注释代码运转。

image.png
运转成功,通过动态向元类增加办法lg_instanceMethod处理了sayByeBye办法找不到,引起的溃散问题。

总结

咱们能够在给公共父类NSObject增加分类,重写办法完结resolveInstanceMethod:,这样就能够成功处理所有imp找不到引起的溃散问题了,同时咱们也能够通过办法命名标准知道是哪个模块的哪个功用引起的问题,做BUG收集。
缺点:

  • 假如要对各个功用进行不同处理,需求许多业务功用逻辑判别,变得很复杂;
  • NSObject的分类中,许多体系的办法处理也会进来这里,会降低运转效率。

音讯转发

假如咱们未完结动态抉择办法resolveInstanceMethod:,调用未完结的办法体系就会溃散,这是咱们通过bt指令检查仓库信息
image.png
是通过doesNotRecognizeSelector:抛出错误的,在这之前还会通过_CF_forwarding_prep_0___forwarding___流程,这都是在CoreFoundation库里,苹果并没有真正开源CoreFoundation库。

image.png

在查阅苹果文档动态办法解析中,提到了音讯转发 Message Forwarding,音讯转发正是苹果给你供给的imp找不到的第2次时机。

Sending a message to an object that does not handle that message is an error. However, before announcing the error, the runtime system gives the receiving object a second chance to handle the message.

原理

  • 当目标发送的sel找不到imp时,体系会发送forwardInvocation:来告诉该目标;
  • forwardInvocation:是从NSObject承继而来的,而在NSObject中仅仅调用doesNotRecognizeSelector:抛出错误;
  • 咱们能够重写forwardInvocation:来完结音讯的转发。
  • forwardInvocation:的唯一参数是NSInvocation
  • NSInvocation中,能够指定音讯接收者target,调用invoke完结音讯转发;
  • 音讯转发完结,回来值将会回来给原始音讯发送者;

文档里还介绍了有意思的点,在咱们自己的类中要完结一个特定功用的办法,该功用已经在别的类中完结,咱们又不能承继这个类,由于或许规划在不同的模块中,那咱们就能够通过forwardInvocation:把音讯转发特有的类去完结,这就完结相似多承继了。

image.png

实例

在关于forwardInvocation:的api文档中,有个重要提示:
image.png

除了forwardInvocation:之外,还必要重写methodSignatureForSelector:转发音讯的机制使用从methodSignatureForSelector取得的信息来创立要转发的NSInvocation目标。重写办法有必要为给定的选择器供给恰当的办法签名办法签名能够是预先制定的,也能够是向另一个目标恳求办法签名

LGTeacher中加上methodSignatureForSelectorforwardInvocation:办法后,再调用sayHello运转

image.png
成功运转,进入methodSignatureForSelectorforwardInvocation:办法,没有引起溃散。
界说LGStudent完结sayHello办法,在LGTeacher中的forwardInvocation:,将音讯转发给LGStudent

image.png

同样,在类办法的音讯流程转发中,跟实例办法的完结是共同的,不过办法上要注意是+

image.png

forwarding​Target​For​Selector

在文档 forward​Invocation中还供给了forwardingTargetForSelector:办法

image.png

该办法的首要效果

无法识别的音讯首先应指向的目标

假如目标完结(或承继)此办法,并回来非nil(和非self)成果,则回来的目标将用作新的接收方目标音讯分派将恢复到该新目标。(明显,假如从该办法回来self,代码将堕入无限循环。)

假如您在非根类中完结此办法,假如您的类对于给定的选择器没有要回来的内容,那么您应该回来调用super完结的成果。

该办法为目标供给了一个时机,在更贵重的forwardInvocation:机械接管之前重定向发送给它的未知音讯。当您只想将音讯重定向到另一个目标,而且能够比常规转发快一个数量级时,这非常有用。假如转发的目标是捕获NSInvocation,或在转发进程中操作参数回来值,则此选项不适用

  • 大概意思是,假如只需求对未识别的音讯,做简略的重定向,那么用此办法会比forwardInvocation:流程快许多许多,假如有额定的复杂操作,如捕获NSInvocation操作参数回来值则不适用该办法。
  • forwardingTargetForSelector:的调用要在forwardInvocation:的前面。

image.png

forwardingTargetForSelector:完结重定向到LGStudent

image.png

流程图

KC音讯转发机制.png

音讯总结

到这里整个Runtime中对音讯的处理流程objc_msgSend的解读都已完结,或许不行全面,中间有些细节难免有遗失,不过重在整个流程的思维探究。

  1. 办法调用 –> objc_msgSend(id receiver, Sel)
  2. 通过isa获取类的地址cls
  3. 快速查找流程CacheLookup
  4. 慢速查找流程lookUpImpOrForward
  5. 动态办法解析resolveMethod_locked;
  6. 音讯重定向forwardingTargetForSelector:;
  7. 音讯转发forwardInvocation:

有爱好的同伴也能够搞一份源码跑一跑
iOS 全网最新objc4 可调式/编译源码

还有编译好的源码的下载地址