OC底层原理07:消息流程之慢速查找

1,896 阅读11分钟

引入

通过测试代码演示方法查找流程:

isa和superclass走向流程图:

image.png

1.LGPerson实例方法 只实现不声明 在NSObject分类中只声明不实现 正常运行 调用LGPerson的实例方法实现.

//NSObject分类.m
- (void)sayEasy{
  NSLog(@"%s",__func__);
}
//调用
[student performSelector: @selector(sayEasy)];

调用NSObject的- (void)sayEasy方法 正常运行。 2.LGPerson类方法 只声明不实现 在NSObject分类中只实现不声明 正常运行 调用NSObject分类中实现.

    //NSObject分类.m
    - (void)sayEasy{
        NSLog(@"%s",__func__);
    }
    + (void)sayEasy{
        NSLog(@"%s",__func__);
    }
    //调用 方法未声明,通过performSelector方式调用
    [LGStudent performSelector: @selector(sayEasy)];
  • 如果根元类+ (void)sayEasy实现了,则会调用根元类+ (void)sayEasy;
  • 如果根元类+ (void)sayEasy没有实现,会沿着superclass找到NSObject类,会调用NSObject类的- (void)sayEasy。

objc_msgSend 慢速查找

objc_msgSend消息流程快速查找中我们分析了快速查找流程,如果快速查不到,则需要进入慢速查找流程。 在快速查找流程中,如果没有找到方法实现,无论是走到CheckMiss还是JumpMiss,最终都会走到__objc_msgSend_uncached汇编函数.

__objc_msgSend_uncached

  • objc-msg-arm64.s文件中查找__objc_msgSend_uncached的汇编实现,其中的核心是MethodTableLookup(即查询方法列表),其源码如下:
    STATIC_ENTRY __objc_msgSend_uncached
    UNWIND __objc_msgSend_uncached, FrameWithNoSaves
    
    // THIS IS NOT A CALLABLE C FUNCTION
    // Out-of-band p16 is the class to search
    
    MethodTableLookup // 开始查询方法列表
    TailCallFunctionPointer x17
    
    END_ENTRY __objc_msgSend_uncached
    
  • 搜索MethodTableLookup的汇编实现,其中的核心是_lookUpImpOrForward,汇编源码实现如下:
.macro MethodTableLookup
    
    // push frame
    SignLR
    stp fp, lr, [sp, #-16]!
    mov fp, sp

    // save parameter registers: x0..x8, q0..q7
    sub sp, sp, #(10*8 + 8*16)
    stp q0, q1, [sp, #(0*16)]
    stp q2, q3, [sp, #(2*16)]
    stp q4, q5, [sp, #(4*16)]
    stp q6, q7, [sp, #(6*16)]
    stp x0, x1, [sp, #(8*16+0*8)]
    stp x2, x3, [sp, #(8*16+2*8)]
    stp x4, x5, [sp, #(8*16+4*8)]
    stp x6, x7, [sp, #(8*16+6*8)]
    str x8,     [sp, #(8*16+8*8)]

    // lookUpImpOrForward(obj, sel, cls, LOOKUP_INITIALIZE | LOOKUP_RESOLVER)
    // receiver and selector already in x0 and x1
    mov x2, x16
    mov x3, #3
    bl  _lookUpImpOrForward //核心源码

    // IMP in x0
    mov x17, x0
    
    // restore registers and return
    ldp q0, q1, [sp, #(0*16)]
    ldp q2, q3, [sp, #(2*16)]
    ldp q4, q5, [sp, #(4*16)]
    ldp q6, q7, [sp, #(6*16)]
    ldp x0, x1, [sp, #(8*16+0*8)]
    ldp x2, x3, [sp, #(8*16+2*8)]
    ldp x4, x5, [sp, #(8*16+4*8)]
    ldp x6, x7, [sp, #(8*16+6*8)]
    ldr x8,     [sp, #(8*16+8*8)]

    mov sp, fp
    ldp fp, lr, [sp], #16
    AuthenticateLR

.endmacro

测试代码验证上述汇编流程

//LGPerson.h
- (void)say666;

//LGPerson.m
//- (void)say666 未实现

//main.m
LGPerson *person = [LGPerson alloc];
[person say666];
  1. 在源码测试代码[person say666]打断点,打开汇编调试【Debug -- Debug worlflow -- 勾选Always show Disassembly】 image.png
  2. 汇编中objc_msgSend加一个断点,执行断住,按住control + stepinto,进入objc_msgSend的汇编 image.png
  3. _objc_msgSend_uncached加一个断点,执行断住,按住control + stepinto,进入汇编。 image.png 从上可以看出最后走到的就是lookUpImpOrForward,此时并不是汇编实现。

源码方法查找:
汇编调用c/c++方法,c/c++方法实现里少一个下划线;反之加下划线。

所以源码里全局搜索lookUpImpOrForward实现,来到慢速查找的c/c++部分。在objc-runtime-new.mm文件中lookUpImpOrForward源码如下:

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

    // 快速查找,如果找到则直接返回imp
    //目的:防止多线程操作时,刚好调用函数,此时缓存进来了
    if (fastpath(behavior & LOOKUP_CACHE)) { 
        imp = cache_getImp(cls, sel);
        if (imp) goto done_nolock;
    }
    
    //加锁,目的是保证读取的线程安全
    runtimeLock.lock();
    
    //判断是否是一个已知的类:判断当前类是否是已经被认可的类,即已经加载的类
    checkIsKnownClass(cls); 
    
    //判断类是否实现,如果没有,需要先实现,此时的目的是为了确定父类链,方便后续的for循环 
    if (slowpath(!cls->isRealized())) { 
        cls = realizeClassMaybeSwiftAndLeaveLocked(cls, runtimeLock);//详细见下方单独说明
    }

    //判断类是否初始化,如果没有,递归初始化所有的类 
    if (slowpath((behavior & LOOKUP_INITIALIZE) && !cls->isInitialized())) { 
    //initializeAndLeaveLocked递归初始化所有的类,调用callInitialize方法。callInitialize方法是给cls发送initialize
        cls = initializeAndLeaveLocked(cls, inst, runtimeLock);
    }

    runtimeLock.assertLocked();
    curClass = cls;

    //----下面for循环是我们研究的重心 
    
    // unreasonableClassCount -- 表示类的迭代的上限
    //(猜测这里递归的原因是attempts在第一次循环时作了减一操作,然后再次循环时,仍在上限的范围内,所以可以继续递归)
    for (unsigned attempts = unreasonableClassCount();;) { 
        //---当前类方法列表(采用二分查找算法),如果找到,则返回,将方法缓存到cache中
        Method meth = getMethodNoSuper_nolock(curClass, sel);//详细见下方单独说明
        if (meth) {
            imp = meth->imp;
            goto done;
        }
        //当前类 = 当前类的父类,并判断父类是否为nil
        if (slowpath((curClass = curClass->superclass) == nil)) {
            //--未找到方法实现,方法解析器也不行,使用转发
            imp = forward_imp;
            break;
        }

        // 如果父类链中存在循环,则停止
        if (slowpath(--attempts == 0)) {
            _objc_fatal("Memory corruption in class list.");
        }

        // --父类缓存
        imp = cache_getImp(curClass, sel);//详细见下方单独说明
        if (slowpath(imp == forward_imp)) { 
            // 如果在父类缓存中找到了imp,imp是forward消息转发,则停止查找,且不缓存,首先调用此类的方法解析器
            break;
        }
        if (fastpath(imp)) {
            //如果在父类缓存中,找到了此方法,将其存储到cache中
            goto done;
        }
    }

    //没有找到方法实现,尝试一次方法解析

    if (slowpath(behavior & LOOKUP_RESOLVER)) {
        //动态方法决议的控制条件,表示流程只走一次
        //^异或操作 相同为0,不同为1
        behavior ^= LOOKUP_RESOLVER; 
        return resolveMethod_locked(inst, sel, cls, behavior);
    }

// 顺序走到这 说明找到imp了
 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;
}

整体的慢速查找流程如图所示:

image.png

lookUpImpOrForward

lookUpImpOrForward里初始:
IMP forward_imp = (IMP)_objc_msgForward_impcache;
IMP imp = nil;
主要有以下几步:

  • 【第一步】当前类cache缓存中cache_getImp查找,即快速查找,找到则直接返回imp,反之,则进入【第二步】

  • 【第二步】判断cls

    • checkIsKnownClass判断是否是已知类,如果不是,则报错
    • 类是否实现,如果没有,则需要realizeClassMaybeSwiftAndLeaveLocked先实现,确定其父类链,此时实例化的目的是为了确定父类链、ro、以及rw等,方法后续数据的读取以及查找的循环
    • 是否初始化,如果没有,则initializeAndLeaveLocked初始化
  • 【第三步】for循环,按照类继承链 或者 元类继承链的顺序查找

    • 当前cls的方法列表中使用二分查找算法查找方法,如果找到,给imp赋值,并进入cache写入流程(在cache 原理分析文章中已经详述过)log_and_fill_cache

    • 当前cls被赋值为父类,如果父类等于nil,则imp = forward_imp消息转发,并终止递归,进入【第四步】

    • 如果父类链中存在循环,则报错,终止循环

      // Halt if there is a cycle in the superclass chain.
      if (slowpath(--attempts == 0)) {
          _objc_fatal("Memory corruption in class list.");
      }
      
    • 父类缓存中查找方法

      • 如果imp = forward_imp消息转发,则跳出for循环
      • 如果imp找到,并且不是forward_imp,则直接返回imp,执行cache写入流程
  • 【第四步】for循环跳出后,如果未找到任何实现,则尝试一次方法解析resolveMethod_locked

realizeClassMaybeSwiftAndLeaveLocked

realizeClassMaybeSwiftAndLeaveLocked主要是为了确定父类链,为后续的for循环做数据的准备。
realizeClassMaybeSwiftAndLeaveLocked内部调用 realizeClassMaybeSwiftMaybeRelockrealizeClassMaybeSwiftMaybeRelock调用realizeClassWithoutSwiftrealizeClassWithoutSwift主要源码如下:

static Class realizeClassWithoutSwift(Class cls, Class previously)
{
    runtimeLock.assertLocked();

    class_rw_t *rw;
    Class supercls;
    Class metacls;

    if (!cls) return nil;
    if (cls->isRealized()) return cls;
    ASSERT(cls == remapClass(cls));

    // fixme verify class is not in an un-dlopened part of the shared cache?

    auto ro = (const class_ro_t *)cls->data();
    auto isMeta = ro->flags & RO_META;
    if (ro->flags & RO_FUTURE) {
        // This was a future class. rw data is already allocated.
        rw = cls->data();
        ro = cls->data()->ro();
        ASSERT(!isMeta);
        cls->changeInfo(RW_REALIZED|RW_REALIZING, RW_FUTURE);
    } else {
        // Normal class. Allocate writeable class data.
        rw = objc::zalloc<class_rw_t>();
        rw->set_ro(ro);
        rw->flags = RW_REALIZED|RW_REALIZING|isMeta;
        cls->setData(rw);
    }

#if FAST_CACHE_META
    if (isMeta) cls->cache.setBit(FAST_CACHE_META);
#endif

    // Choose an index for this class.
    // Sets cls->instancesRequireRawIsa if indexes no more indexes are available
    cls->chooseClassArrayIndex();

    if (PrintConnecting) {
        _objc_inform("CLASS: realizing class '%s'%s %p %p #%u %s%s",
                     cls->nameForLogging(), isMeta ? " (meta)" : "", 
                     (void*)cls, ro, cls->classArrayIndex(),
                     cls->isSwiftStable() ? "(swift)" : "",
                     cls->isSwiftLegacy() ? "(pre-stable swift)" : "");
    }

    // Realize superclass and metaclass, if they aren't already.
    // This needs to be done after RW_REALIZED is set above, for root classes.
    // This needs to be done after class index is chosen, for root metaclasses.
    // This assumes that none of those classes have Swift contents,
    //   or that Swift's initializers have already been called.
    //   fixme that assumption will be wrong if we add support
    //   for ObjC subclasses of Swift classes.
    //---- 递归找supercls metacls 把类和元类的继承链确定下来 为了后面查找父类cache
    supercls = realizeClassWithoutSwift(remapClass(cls->superclass), nil);
    metacls = realizeClassWithoutSwift(remapClass(cls->ISA()), nil);
    ...
    // Update superclass and metaclass in case of remapping
    //---- 把supercls和metacls放进去
    cls->superclass = supercls;
    cls->initClassIsa(metacls);

    // Reconcile instance variable offsets / layout.
    // This may reallocate class_ro_t, updating our ro variable.
    if (supercls  &&  !isMeta) reconcileInstanceVariables(cls, supercls, ro);

    // Set fastInstanceSize if it wasn't set already.
    cls->setInstanceSize(ro->instanceSize);

    // Copy some flags from ro to rw
    if (ro->flags & RO_HAS_CXX_STRUCTORS) {
        cls->setHasCxxDtor();
        if (! (ro->flags & RO_HAS_CXX_DTOR_ONLY)) {
            cls->setHasCxxCtor();
        }
    }
    
    // Propagate the associated objects forbidden flag from ro or from
    // the superclass.
    if ((ro->flags & RO_FORBIDS_ASSOCIATED_OBJECTS) ||
        (supercls && supercls->forbidsAssociatedObjects()))
    {
        rw->flags |= RW_FORBIDS_ASSOCIATED_OBJECTS;
    }

    // Connect this class to its superclass's subclass lists
    if (supercls) {
        addSubclass(supercls, cls);
    } else {
        addRootClass(cls);
    }

    // Attach categories
    //---- 把方法列表、属性、protocol都贴到rwe(rw->ext())中去
    methodizeClass(cls, previously);

    return cls;
}

说明:

  • 会对data()ro()赋值
  • 递归找superclsmetacls 把类和元类的继承链确定下来 为了后面查找父类cache
  • 通过cls->superclass = supercls;addSubclass(supercls, cls);互相确认父子关系看出class是双向链表
  • methodizeClass方法中把方法列表、属性、protocol都贴到rwe(rw->ext())中去
getMethodNoSuper_nolock 查看当前类curClass中是否有方法

流程如下所示:
image.png

static method_t *
getMethodNoSuper_nolock(Class cls, SEL sel)
{
    ...
    auto const methods = cls->data()->methods();
    for (auto mlists = methods.beginLists(),
              end = methods.endLists();
         mlists != end;
         ++mlists)
    {
        // <rdar://problem/46904873> getMethodNoSuper_nolock is the hottest
        // caller of search_method_list, inlining it turns
        // getMethodNoSuper_nolock into a frame-less function and eliminates
        // any store from this codepath.
        method_t *m = search_method_list_inline(*mlists, sel);//二分查找方法列表
        if (m) return m;
    }

    return nil;
}

核心会调用search_method_list_inline,其二分查找核心的源码实现如下:

static method_t *
findMethodInSortedMethodList(SEL key, const method_list_t *list)
{
    ASSERT(list);
    const method_t * const first = &list->first;
    const method_t *base = first;
    const method_t *probe;
    uintptr_t keyValue = (uintptr_t)key;
    uint32_t count;
    
    //sel在method_list中是递增的
    for (count = list->count; count != 0; count >>= 1) {
        probe = base + (count >> 1);
        
        uintptr_t probeValue = (uintptr_t)probe->name;
        
        if (keyValue == probeValue) {
            // `probe` is a match.
            // Rewind looking for the *first* occurrence of this value.
            // This is required for correct category overrides.
            //---- 当找到sel 并且分类里有同名方法 需要probe--
            //方法先加载类 然后再加载分类方法
            while (probe > first && keyValue == (uintptr_t)probe[-1].name) {
                probe--;
            }
            return (method_t *)probe;
        }
        
        if (keyValue > probeValue) {
            base = probe + 1;
            count--;
        }
    }
    
    return nil;
}

从第一次查找开始,每次都取中间位置,与想查找的key的value值作比较,如果相等,则需要排除分类方法,然后将查询到的位置的方法实现返回,如果不相等,则需要继续二分查找,如果循环至count = 0还是没有找到,则直接返回nil,如下所示: image.png 以查找LGPerson类的say666实例方法为例,其二分查找过程如下: image.png

getMethodNoSuper_nolock查找完毕,回到lookUpImpOrForwar方法 如果meth有值 ,则给imp赋值并且缓存方法goto done

  • goto done image.png log_and_fill_cache源码如下:
static void
log_and_fill_cache(Class cls, IMP imp, SEL sel, id receiver, Class implementer)
{
#if SUPPORT_MESSAGE_LOGGING
    if (slowpath(objcMsgLogEnabled && implementer)) {
        bool cacheIt = logMessageSend(implementer->isMetaClass(), 
                                      cls->nameForLogging(),
                                      implementer->nameForLogging(), 
                                      sel);
        if (!cacheIt) return;
    }
#endif
    cache_fill(cls, sel, imp, receiver);//把方法写入缓存
}

cache_fill把方法写入缓存.为了下次调用此方法时使用快速查找,提高性能。

总结下流程: objc_msgSend查找缓存->缓存没有则慢速二分查找->如果找到则cache_fill缓存方法->下次还调用此方法则用objc_msgSend查找缓存快速流程。

如果慢速二分查找没找到,则会找superclass cache (**if** (slowpath((curClass = curClass->getSuperclass()) == **nil**)) (已经把superclass赋值给curClass了), 继续走lookUpImpOrForward本次for循环后续流程。

cache_getImp方法:父类缓存查找
STATIC_ENTRY _cache_getImp

    GetClassFromIsa_p16 p0
    CacheLookup GETIMP, _cache_getImp

LGetImpMiss:
    mov p0, #0
    ret
END_ENTRY _cache_getImp

cache_getImp方法是通过汇编_cache_getImp实现,传入的$0 是 GETIMP,如下所示: image.png

  • 如果父类缓存中找到了方法实现,则跳转至CacheHit即命中,则直接返回imp

  • 如果在父类缓存中,没有找到方法实现,则跳转至CheckMiss 或者 JumpMiss,通过判断$0 跳转至LGetImpMiss,直接返回nil

resolveMethod_locked 进入动态方法决议

跳出for循环,此时imp=forward_imp,没有找到。 继续往下会走到下面方法:

// No implementation found. Try method resolver once.
if (slowpath(behavior & LOOKUP_RESOLVER)) { //动态方法决议只会来一次
    behavior ^= LOOKUP_RESOLVER;
    return resolveMethod_locked(inst, sel, cls, behavior);//进入动态方法决议
}

resolveMethod_locked进入动态方法决议 image.png

_objc_msgForward_impcache

在上面lookUpImpOrForward源码中forward_imp默认是_objc_msgForward_impcacheimage.png

image.png

  • 其中_objc_msgForward_impcache是汇编实现,会跳转至__objc_msgForward,其核心是__objc_forward_handler
STATIC_ENTRY __objc_msgForward_impcache

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

汇编实现中查找__objc_forward_handler,并没有找到,在源码中去掉一个下划线进行全局搜索_objc_forward_handler,有如下实现,本质是调用c/c++的objc_defaultForwardHandler方法:

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

这就是我们在日常开发中最常见的错误:没有实现函数,运行程序,崩溃时报的错误提示

lookUpImpOrForward流程总结

  • 对于对象方法(即实例方法),即在类中查找,其慢速查找的父类链是:类--父类--根类--nil

  • 对于类方法,即在元类中查找,其慢速查找的父类链是:元类--根元类--根类--nil

  • 如果快速查找、慢速查找也没有找到方法实现,则尝试动态方法决议

  • 如果动态方法决议仍然没有找到,则进行消息转发 下文中将讲述方法实现未找到报错前我们可以做的补救和消息转发流程

文章列表

博客文章列表

本文参考 在此致谢