美文网首页
iOS objc_msgSend流程分析

iOS objc_msgSend流程分析

作者: qinghan | 来源:发表于2021-06-29 18:17 被阅读0次

找到objc_msgSend

调用方法,打断点

图片一.png
通过汇编发现调用objc_msgSendstep into,发现objc源码里面实现
图片二.png
通过objc源码找到objc_msgSend 的实现入口,其中不同的架构有不同的实现,这里我们主要看arm64
图片三.png

汇编分析

    ENTRY _objc_msgSend
    UNWIND _objc_msgSend, NoFrame

    cmp p0, #0          // nil check and tagged pointer check
#if SUPPORT_TAGGED_POINTERS
    b.le    LNilOrTagged        //  (MSB tagged pointer looks negative)
#else
    b.eq    LReturnZero
#endif
    ldr p13, [x0]       // p13 = isa
    GetClassFromIsa_p16 p13, 1, x0  // p16 = class
LGetIsaDone:
    // calls imp or objc_msgSend_uncached
    CacheLookup NORMAL, _objc_msgSend, __objc_msgSend_uncached

#if SUPPORT_TAGGED_POINTERS
LNilOrTagged:
    b.eq    LReturnZero     // nil check
    GetTaggedClass
    b   LGetIsaDone
// SUPPORT_TAGGED_POINTERS
#endif

LReturnZero:
    // x0 is already zero
    mov x1, #0
    movi    d0, #0
    movi    d1, #0
    movi    d2, #0
    movi    d3, #0
    ret

    END_ENTRY _objc_msgSend
  1. 先进入ENTRY _objc_msgSend方法
  2. cmp p0, #0检查对象是否为空p0 上是对象本身, cmp比较
  3. 如果为空则跳转到LReturnZerob.le小于等于并跳转,b.eq等于并跳转
    4.ldr p13, [x0],读取x0寄存器的值,赋值给p13,这里的p13实际上是lisa
    5.GetClassFromIsa_p16 p13, 1, x0,通过isa 获取到对象的class
    6.CacheLookup通过缓存查找

GetClassFromIsa_p16分析

.macro GetClassFromIsa_p16 src, needs_auth, auth_address /* note: auth_address is not required if !needs_auth */
#if SUPPORT_INDEXED_ISA
    // Indexed isa
...省略
#elif __LP64__
.if \needs_auth == 0 // _cache_getImp takes an authed class already
    mov p16, \src
.else
    // 64-bit packed isa
    ExtractISA p16, \src, \auth_address
.endif
#else
    // 32-bit raw isa
    mov p16, \src
#endif
.endmacro

INDEXED_ISA表示直接是一个raw isa,PACKED_ISA,需要通过一个掩码取获取,那么这段代码实际上只是调用了 ExtractISA 方法,
进入ExtractISA


#if __has_feature(ptrauth_calls)
.macro ExtractISA
    and $0, $1, #ISA_MASK
#if ISA_SIGNING_AUTH_MODE == ISA_SIGNING_STRIP
    xpacd   $0
#elif ISA_SIGNING_AUTH_MODE == ISA_SIGNING_AUTH
    mov x10, $2
    movk    x10, #ISA_SIGNING_DISCRIMINATOR, LSL #48
    autda   $0, x10
#endif
.endmacro
#else
.macro ExtractISA
    and    $0, $1, #ISA_MASK
.endmacro
  • __has_feature(ptrauth_calls): 是判断编译器是否支持指针身份验证功能
  • ptrauth_calls 指针身份验证,针对arm64e架构;使用Apple A12或更高版本A系列处理器的设备(如iPhone XS、iPhone XS Max和iPhone XR或更新的设备)支持-arm64e架构
    上面的机型的判断并不影响阅读源码,最主要还是and $0, $1, #ISA_MASK
    0 表示p16,1表示src,也就是p13,and是与运算,p16 = p13 & ISA_MASK
    ISA_MASK 是isa里面宏定义的掩码,所以p16就是对象的class指针

CacheLookup分析

CacheLookup,这个方法是快速查找的核心
首先一进来会有架构的判断,我们找到真机的方法

 CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16
    ldr p11, [x16, #CACHE]  
  • x16class的指针,CACHE的大小为16个字节,也就是p11位cache位置的指针
#if __has_feature(ptrauth_calls)
    tbnz    p11, #0, LLookupPreopt\Function
    and p10, p11, #0x0000ffffffffffff   // p10 = buckets
#else
    and p10, p11, #0x0000fffffffffffe   // p10 = buckets
    tbnz    p11, #0, LLookupPreopt\Function
#endif
    eor p12, p1, p1, LSR #7
    and p12, p12, p11, LSR #48  
  • 我们以 A12为准,tbnz p11, #0, LLookupPreopt\Functiontbnz:表示如果测试位不为0则跳转到LLookupPreopt方法,LLookupPreopt这个表示从共享缓存去加载(UIKit,Foundation),在这里不做过多的分析,
  • and p10, p11, #0x0000ffffffffffff 表示p10 = p11 & 0x0000ffffffffffff, 那么p10表示bucket首地址
  • eor p12, p1, p1, LSR #7and p12, p12, p11, LSR #48这两句实际上通过 sel 拿到bucket index, p1表示sel,LSR右移,eor异或操作,p12 ,其实是bucket的索引
    add p13, p10, p12, LSL #(1+PTRSHIFT)
                        // p13 = buckets + ((_cmd & mask) << (1+PTRSHIFT))
                        // do {
1:  ldp p17, p9, [x13], #-BUCKET_SIZE   //     {imp, sel} = *bucket--
    cmp p9, p1              //     if (sel != _cmd) {
    b.ne    3f              //         scan more
                        //     } else {
2:  CacheHit \Mode              // hit:    call or return imp
                        //     }
3:  cbz p9, \MissLabelDynamic       //     if (sel == 0) goto Miss;
    cmp p13, p10            // } while (bucket >= buckets)
    b.hs    1b
  • add p13, p10, p12, LSL #(1+PTRSHIFT) ,p13 = p10 + p12 << 4,
    那么p13 实际上就是当前发送方法的 bucketbucket存储的是{imp,sel}
  • 将imp,sel分别保存到p17 和p9,然后遍历buckets去对比,如果发现有和调用的sel和buckets有相同的,就直接返回imp,也就是CacheHit缓存命中,
  • 如果遍历都没有找到,即cbz p9位0,即sel为空,那么跳转到MissLabelDynamic,即没有找到。
    用一张图总结bucket查找过程:
    bucket查找.png

缓存没有找到

缓存没有找到则会进入__objc_msgSend_uncached

CacheLookup NORMAL, _objc_msgSend,__objc_msgSend_uncached

继续往下走:

STATIC_ENTRY __objc_msgSend_uncached
UNWIND __objc_msgSend_uncached, FrameWithNoSaves

// THIS IS NOT A CALLABLE C FUNCTION
// Out-of-band p15 is the class to search
MethodTableLookup
TailCallFunctionPointer x17
END_ENTRY __objc_msgSend_uncached

发现会调用MethodTableLookup,最后会调用lookUpImpOrForward方法,发现
lookUpImpOrForward在汇编里面没有找到实现,发现实现在objc-runtime-new.mm里面,在此就近入方法的慢速查找流程。

lookUpImpOrForward分析

准备工作

IMP lookUpImpOrForward(id inst, SEL sel, Class cls, int behavior)
{
//准备工作
  if (slowpath(!cls->isInitialized())) {
        behavior |= LOOKUP_NOCACHE;
    }
   checkIsKnownClass(cls);
  cls = realizeAndInitializeIfNeeded_locked(inst, cls, behavior & LOOKUP_INITIALIZE);

//循环查找
 for (unsigned attempts = unreasonableClassCount();;){
}
 return imp;
}
  • cls->isInitialized() 检测类是否初始化
  • checkIsKnownClass 检测类是否已经注册
  • realizeAndInitializeIfNeeded_locked检测类是否已经实现,没有实现会递归去实现父类和元类

二分查找流程(重点)

循环查找

for (unsigned attempts = unreasonableClassCount();;) {
        if (curClass->cache.isConstantOptimizedCache(/* strict */true)) {
#if CONFIG_USE_PREOPT_CACHES
            imp = cache_getImp(curClass, sel);
            if (imp) goto done_unlock;
            curClass = curClass->cache.preoptFallbackClass();
#endif
        } else {
            // curClass method list.
            Method meth = getMethodNoSuper_nolock(curClass, sel);
            if (meth) {
                imp = meth->imp(false);
                goto done;
            }

            if (slowpath((curClass = curClass->getSuperclass()) == nil)) {
                // No implementation found, and method resolver didn't help.
                // Use forwarding.
                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);
        if (slowpath(imp == forward_imp)) {
            // Found a forward:: entry in a superclass.
            // Stop searching, but don't cache yet; call method
            // resolver for this class first.
            break;
        }
        if (fastpath(imp)) {
            // Found the method in a superclass. Cache it in this class.
            goto done;
        }
    }
  • cache.isConstantOptimizedCache先从缓存中判断是否存在,如果存在直接从缓存中取
  • getMethodNoSuper_nolock,如果没有找到从类的methods里面取找

getMethodNoSuper_nolock分析

 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;
    }
  • 可以看出methods是一个二维数字,methods-> mlists
  • 真正的查找是在mlists里面
    继续进入search_method_list_inline
    在这个里面有两个方法,
  1. findMethodInSortedMethodList已经拍好序的
  2. findMethodInUnsortedMethodList没有排好序的
    我们知道如果要进行二分查找,数组必须有序,如果是没有排序的直接遍历数组去找。
    没有排序,循环遍历
findMethodInUnsortedMethodList(SEL sel, const method_list_t *list, const getNameFunc &getName)
{
    for (auto& meth : *list) {
        if (getName(meth) == sel) return &meth;
    }
    return nil;
}

排序的,二分查找:

findMethodInSortedMethodList(SEL key, const method_list_t *list, const getNameFunc &getName)
{
    ASSERT(list);

    auto first = list->begin();
    auto base = first;
    decltype(first) probe;

    uintptr_t keyValue = (uintptr_t)key;
    uint32_t count;
    
    for (count = list->count; count != 0; count >>= 1) {
        probe = base + (count >> 1);
        
        uintptr_t probeValue = (uintptr_t)getName(probe);
        
        if (keyValue == probeValue) {
            // `probe` is a match.
            // Rewind looking for the *first* occurrence of this value.
            // This is required for correct category overrides.
            while (probe > first && keyValue == (uintptr_t)getName((probe - 1))) {
                probe--;
            }
            return &*probe;
        }
        
        if (keyValue > probeValue) {
            base = probe + 1;
            count--;
        }
    }
 
    return nil;
}
  • 苹果的工程是用的很巧妙,通过右移来实现折半查找
  • 这里有一个主意的点,就是我们发现即使找到了还有一个循环,probe--,这里实际上是因为如果有分类的方法和类的方法一样的话,会优先使用分类的方法,This is required for correct category overrides.注释里面也给出了解释

如果自身的类没有找到,就会查找父类,一值到跟类。

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

如果都没有找到就会走消息转发forward_imp

总结

上面就是方法的二分查找,其实消息的发送还没有结束,如果自身类,父类,根类(NSObject)都没有找到就会走转发流程也就是forward_imp

补充(initialize)

还有一个关于initialize方法的调用,我们再调用其他方法是也可能会走initialize方法,这个方法在我们平时的开发中也是比较熟悉的
objc源码中可以看到如下的调用顺序
lookupimporforward-> realizeAndInitializeIfNeeded_locked-> initializeAndLeaveLocked-> initializeAndMaybeRelock-> initializeNonMetaClass-> callInitialize-> initialize

相关文章

网友评论

      本文标题:iOS objc_msgSend流程分析

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