美文网首页程序员
六:消息转发流程二:objc_msgSend

六:消息转发流程二:objc_msgSend

作者: Mr姜饼 | 来源:发表于2020-11-24 16:57 被阅读0次
前言:

上小节,我们讲到了从 cache缓存中查找消息的imp,如果没有找到的话,继续往下查找,也就是所谓的慢速查找流程,即从类的方法列表中去查找。

  • 断点调试验证:

step1:

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // insert code here...
        
        LGPerson *person = [LGPerson alloc];
        [person sayHello];  1断点
//        [person say666];

    }
    return 0;
}

step2:

image.png

step3: step into(未从缓存中找到imp,发送_objc_msgSend_uncached消息)

image.png

step4: step into(然后开始进入慢速查找imp流程)

image.png

核心代码:

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

    runtimeLock.assertUnlocked();

    // Optimistic cache lookup
    if (fastpath(behavior & LOOKUP_CACHE)) {
        imp = cache_getImp(cls, sel);
        if (imp) goto done_nolock;
    }

    runtimeLock.lock();

    checkIsKnownClass(cls);

    if (slowpath(!cls->isRealized())) {
        cls = realizeClassMaybeSwiftAndLeaveLocked(cls, runtimeLock);
        // runtimeLock may have been dropped but is now locked again
    }

    if (slowpath((behavior & LOOKUP_INITIALIZE) && !cls->isInitialized())) {
        cls = initializeAndLeaveLocked(cls, inst, runtimeLock);
       
    }

    runtimeLock.assertLocked();
    curClass = cls;

   
    for (unsigned attempts = unreasonableClassCount();;) {
        // curClass method list.
        Method meth = getMethodNoSuper_nolock(curClass, sel);
        if (meth) {
            imp = meth->imp;
            goto done;
        }

        if (slowpath((curClass = curClass->superclass) == nil)) {
            // No implementation found, and method resolver didn't help.
            
            imp = forward_imp;
            break;
        }

        // Halt if there is a cycle in the superclass chain.
        if (slowpath(--attempts == 0)) {
            _objc_fatal("Memory corruption in class list.");
        }
        // Superclass cache.
        imp = cache_getImp(curClass, sel); // 有问题???? cache_getImp - lookup - lookUpImpOrForward
        if (slowpath(imp == forward_imp)) {
            
            break;
        }
        if (fastpath(imp)) {
            // Found the method in a superclass. Cache it in this class.
            goto done;
        }
    }

    // No implementation found. Try method resolver once.

    if (slowpath(behavior & LOOKUP_RESOLVER)) {
        behavior ^= LOOKUP_RESOLVER;
        return resolveMethod_locked(inst, sel, cls, behavior);
    }

 done:
    log_and_fill_cache(cls, imp, sel, inst, curClass);
    runtimeLock.unlock();
 done_nolock:
    if (slowpath((behavior & LOOKUP_NIL) && imp == forward_imp)) {
        return nil;
    }
    return imp;
}

分析:
代码块 1:防止多线程干扰,再次从缓存中取一次,确保安全性

    // Optimistic cache lookup
    if (fastpath(behavior & LOOKUP_CACHE)) {
        imp = cache_getImp(cls, sel);
        if (imp) goto done_nolock;
    }

代码块 2:确保类信息加载到内存中去了,并且创建类的双向链表,包括父类已经子类的各种信息

   if (slowpath(!cls->isRealized())) {
        cls = realizeClassMaybeSwiftAndLeaveLocked(cls, runtimeLock);
    }

    if (slowpath((behavior & LOOKUP_INITIALIZE) && !cls->isInitialized())) {
        cls = initializeAndLeaveLocked(cls, inst, runtimeLock);
    }

代码块 3:for无限循环,直到有 " break ",跳出循环

 for (unsigned attempts = unreasonableClassCount();;) {

代码块 4:从curCkass类本身(有可能是父类,因为存在下面面的重新赋值情况)methodList中查找sel,找到的话,返回imp,跳出循环

 Method meth = getMethodNoSuper_nolock(curClass, sel);
        if (meth) {
            imp = meth->imp;
            goto done;
        }

代码块 5:上面的代码块没有找到本类中的实例化方法,所以开始往父类开始找 curClass->superclass,并且判断此时的curClass如果为NSObject的时候,也就是NSObject->superclass == nil 的时候,将imp赋值为forward_imp ,然后跳出循环

if (slowpath((curClass = curClass->superclass) == nil)) {
            // No implementation found, and method resolver didn't help.
            // Use forwarding.
            imp = forward_imp;
            break;
        }

代码块 6:从curClass类的缓存中取找imp

 imp = cache_getImp(curClass, sel); 

代码块 7:如何在循环中并没找到imp,也就是此时的imp== forward_imp的情况下,循环走完了,那么接下来开始找 *动态方法决议流程*

代码块 8:动态方法决议

if (slowpath(behavior & LOOKUP_RESOLVER)) {
        behavior ^= LOOKUP_RESOLVER;
        return resolveMethod_locked(inst, sel, cls, behavior);
    }
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 (!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);
}
static void resolveInstanceMethod(id inst, SEL sel, Class cls)
{
    runtimeLock.assertUnlocked();
    ASSERT(cls->isRealized());
    SEL resolve_sel = @selector(resolveInstanceMethod:);

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

核心代码:
BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
bool resolved = msg(cls, resolve_sel, sel);
重新发送消息,再次确认是否有该sel的imp

  • 动态方法决议(补救措施)
    + (BOOL)resolveInstanceMethod:(SEL)sel ;
    如果在该类中并未实现sel对象方法,可在此方法中动态绑定sel,添加进methods里面
+ (BOOL)resolveInstanceMethod:(SEL)sel{
    
    if (sel == @selector(say666)) {
        NSLog(@"%@ 来了",NSStringFromSelector(sel));
        
        IMP imp           = class_getMethodImplementation(self, @selector(sayMaster));
        Method sayMMethod = class_getInstanceMethod(self, @selector(sayMaster));
        const char *type  = method_getTypeEncoding(sayMMethod);
        return class_addMethod(self, sel, imp, type);
    }
    
    return [super resolveInstanceMethod:sel];
}

+ (BOOL)resolveClassMethod:(SEL)sel;类方法找不到的补救措施

+ (BOOL)resolveClassMethod:(SEL)sel{
    if (sel == @selector(sayTest)) {
        NSLog(@"%@ 来了",NSStringFromSelector(sel));
        IMP imp   = class_getMethodImplementation(objc_getMetaClass("LGPerson"), @selector(tset));
        Method sayMMethod =class_getClassMethod(objc_getMetaClass("LGPerson"), @selector(tset));
        const char *type  = method_getTypeEncoding(sayMMethod);
        return  class_addMethod(objc_getMetaClass("LGPerson") , sel, imp, type);
    }
    return [super resolveClassMethod:sel];
}

当在此两种方法中实现了并绑定了新的sel之后,会重新发起lookupIMP的查找,这时候就能查到,这时候就不会崩溃了!!!!

消息转发

当上述的动态方法决议并没与触发的时候,这时候imp还是_objc_msgForward的时候,这时候进入消息转发流程

  • 快速转发过程

  • 慢速转发过程

利用日志找到消息转发流程
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;
}
#import <Foundation/Foundation.h>
#import "LGPerson.h"

// 慢速查找 
extern void instrumentObjcMessageSends(BOOL flag);

int main(int argc, const char * argv[]) {
    @autoreleasepool {

        LGPerson *person = [LGPerson alloc];
        //开启日志
        instrumentObjcMessageSends(YES);
        [person sayHello];
        //关闭日志
        instrumentObjcMessageSends(NO);
        NSLog(@"Hello, World!");
    }
    return 0;
}

运行项目,之后跑道/tmp目录下查找

image.png
image.png
+ LGPerson NSObject resolveInstanceMethod:
+ LGPerson NSObject resolveInstanceMethod:
- LGPerson NSObject forwardingTargetForSelector:
- LGPerson NSObject forwardingTargetForSelector:
- LGPerson NSObject methodSignatureForSelector:
- LGPerson NSObject methodSignatureForSelector:

forwardingTargetForSelector(快速)methodSignatureForSelector(慢速) 为消息转发过程

消息转发机制.png
扩展

汇编层面,为什么收到_objc_msgForward的imp会报如下的错误,看下图理解

    ENTRY __objc_msgForward
    // Non-stret version

    movq    __objc_forward_handler(%rip), %r11
    jmp *%r11
__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;
2020-11-24 16:24:29.132416+0800 KCObjc[15111:196925] +[LGPerson sss]: unrecognized selector sent to class 0x100002298
2020-11-24 16:24:29.134042+0800 KCObjc[15111:196925] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '+[LGPerson sss]: unrecognized selector sent to class 0x100002298'
*** First throw call stack:
(
    0   CoreFoundation                      0x00007fff34d7eb57 __exceptionPreprocess + 250
    1   libobjc.A.dylib                     0x000000010038db8a objc_exception_throw + 42
    2   CoreFoundation                      0x00007fff34dfdb37 __CFExceptionProem + 0
    3   CoreFoundation                      0x00007fff34ce33bb ___forwarding___ + 1427
    4   CoreFoundation                      0x00007fff34ce2d98 _CF_forwarding_prep_0 + 120
    5   libobjc.A.dylib                     0x00000001003da7b0 +[NSObject performSelector:] + 64
    6   KCObjc                              0x0000000100000b4a main + 74
    7   libdyld.dylib                       0x00007fff6ed72cc9 start + 1
    8   ???                                 0x0000000000000001 0x0 + 1
)

相关文章

网友评论

    本文标题:六:消息转发流程二:objc_msgSend

    本文链接:https://www.haomeiwen.com/subject/aixiiktx.html