美文网首页
Runtime 复习(一)

Runtime 复习(一)

作者: comsubin | 来源:发表于2019-05-22 10:56 被阅读0次

    对象与方法的本质

    新建一个类Person,在mian.m路径下打开终端运行以下命令

    xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m -o testMain.c++
    
    int main(int argc, char * argv[]) {
        @autoreleasepool {
    
            Person *p = [[Person alloc]init];
            [p run];
    //        return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
        }
        return 0;
    }
    

    打开编译后生成的c++文件 发现Persion 对象其实就是个objc_object 的一个结构体,而我们熟知的alloc init 这些方法 前面都多了个sel_registerName 这里涉及到SEL ,然后就是它们前面的 objc_msgSend 这个后面再说.

    • SEL: 方法选择器,它表示的是一个我们熟知的 @selector 指针
    • sel_registerName: 像runtime system 注册一个方法名,返回SEL


      WX20190509-165709.png

    objc_msgSend 先来看源码中是如何实现的

    • ENTRY:是程序入口伪指令。在一个完整的汇编程序中至少有一个 ENTRY.
    • CMP:汇编比较指令.
        ENTRY _objc_msgSend
        UNWIND _objc_msgSend, NoFrame
        MESSENGER_START
        /// x0 recevier
        // 消息接收者  消息名称
        cmp x0, #0          // nil check and tagged pointer check
        b.le    LNilOrTagged        //  (MSB tagged pointer looks negative)
        ldr x13, [x0]       // x13 = isa
        and x16, x13, #ISA_MASK // x16 = class  
    LGetIsaDone:
        CacheLookup NORMAL      // calls imp or objc_msgSend_uncached
    LNilOrTagged:
        b.eq    LReturnZero     // nil check
    
        // tagged
        mov x10, #0xf000000000000000
        cmp x0, x10
        b.hs    LExtTag
        adrp    x10, _objc_debug_taggedpointer_classes@PAGE
        add x10, x10, _objc_debug_taggedpointer_classes@PAGEOFF
        ubfx    x11, x0, #60, #4
        ldr x16, [x10, x11, LSL #3]
        b   LGetIsaDone
    
    LExtTag:
        // ext tagged
        adrp    x10, _objc_debug_taggedpointer_ext_classes@PAGE
        add x10, x10, _objc_debug_taggedpointer_ext_classes@PAGEOFF
        ubfx    x11, x0, #52, #8
        ldr x16, [x10, x11, LSL #3]
        b   LGetIsaDone
        
    LReturnZero:
        // x0 is already zero
        mov x1, #0
        movi    d0, #0
        movi    d1, #0
        movi    d2, #0
        movi    d3, #0
        MESSENGER_END_NIL
        ret
    
        END_ENTRY _objc_msgSend
    
    

    通过源码可知:先进行nil检查,如果是nil,跳转至LNilOrTagged--> LReturnZero(返回nil),如果存在先进行寄存器的一些操作然后跳转至LGetIsaDone(isa处理完毕) 开始缓存查找这里有2种情况一个是直接找到IMP 一个是调用 objc_msgSend_uncached(没找到),先来看cachelookup的实现.

    .macro JumpMiss
    .if $0 == GETIMP
        b   LGetImpMiss
    .elseif $0 == NORMAL
        b   __objc_msgSend_uncached
    .elseif $0 == LOOKUP
        b   __objc_msgLookup_uncached
    .else
    .abort oops
    .endif
    .endmacro
      .macro CacheLookup
        // x1 = SEL, x16 = isa
        ldp x10, x11, [x16, #CACHE] // x10 = buckets, x11 = occupied|mask
        and w12, w1, w11        // x12 = _cmd & mask
        add x12, x10, x12, LSL #4   // x12 = buckets + ((_cmd & mask)<<4)
    
        ldp x9, x17, [x12]      // {x9, x17} = *bucket
    1:  cmp x9, x1          // if (bucket->sel != _cmd)
        b.ne    2f          //     scan more
        CacheHit $0         // call or return imp
        
    2:  // not hit: x12 = not-hit bucket
        CheckMiss $0            // miss if bucket->sel == 0
        cmp x12, x10        // wrap if bucket == buckets
        b.eq    3f
        ldp x9, x17, [x12, #-16]!   // {x9, x17} = *--bucket
        b   1b          // loop
    
    3:  // wrap: x12 = first bucket, w11 = mask
        add x12, x12, w11, UXTW #4  // x12 = buckets+(mask<<4)
    
    

    CacheLookup 这里有3种情况

    • CacheHit 缓存发现
    • CheckMiss 没找到
    • add 添加

    checkMiss后会调用objc_msgSend_uncache

    
        STATIC_ENTRY __objc_msgSend_uncached
        UNWIND __objc_msgSend_uncached, FrameWithNoSaves
    
        // THIS IS NOT A CALLABLE C FUNCTION
        // Out-of-band x16 is the class to search
        
        MethodTableLookup
        br  x17
    
        END_ENTRY __objc_msgSend_uncached
    
    
        STATIC_ENTRY __objc_msgLookup_uncached
        UNWIND __objc_msgLookup_uncached, FrameWithNoSaves
    
        // THIS IS NOT A CALLABLE C FUNCTION
        // Out-of-band x16 is the class to search
        
        MethodTableLookup
        ret
    
        END_ENTRY __objc_msgLookup_uncached
    

    这里出现一个新的MethodTableLookup 方法列表查找.接下来看它的实现

    .macro MethodTableLookup
        
        // push frame
        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)]
    
        // receiver and selector already in x0 and x1
        mov x2, x16
        bl  __class_lookupMethodAndLoadCache3
    
        // 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
    
    .endmacro
    
    

    跳转__class_lookupMethodAndLoadCache3 这是一个C函数通过源码发现该函数实现

    /***********************************************************************
    * _class_lookupMethodAndLoadCache.
    * Method lookup for dispatchers ONLY. OTHER CODE SHOULD USE lookUpImp().
    * This lookup avoids optimistic cache scan because the dispatcher 
    * already tried that.
    **********************************************************************/
    IMP _class_lookupMethodAndLoadCache3(id obj, SEL sel, Class cls)
    {
        return lookUpImpOrForward(cls, sel, obj, 
                                  YES/*initialize*/, NO/*cache*/, YES/*resolver*/);
    }
    retry:    
        runtimeLock.assertReading();
    
        // Try this class's cache.
        //多线程下可能已经查到并缓存了
        imp = cache_getImp(cls, sel);
        if (imp) goto done;
    
        // Try this class's method lists.
        {
            Method meth = getMethodNoSuper_nolock(cls, sel);
            if (meth) {
                log_and_fill_cache(cls, meth->imp, sel, inst, cls);
                imp = meth->imp;
                goto done;
            }
        }
    
        // Try superclass caches and method lists.
        {
            unsigned attempts = unreasonableClassCount();
            for (Class curClass = cls->superclass;
                 curClass != nil;
                 curClass = curClass->superclass)
            {
                // Halt if there is a cycle in the superclass chain.
                if (--attempts == 0) {
                    _objc_fatal("Memory corruption in class list.");
                }
                
                // Superclass cache.
                imp = cache_getImp(curClass, sel);
                if (imp) {
                    if (imp != (IMP)_objc_msgForward_impcache) {
                        // Found the method in a superclass. Cache it in this class.
                        log_and_fill_cache(cls, imp, sel, inst, curClass);
                        goto done;
                    }
                    else {
                        // Found a forward:: entry in a superclass.
                        // Stop searching, but don't cache yet; call method 
                        // resolver for this class first.
                        break;
                    }
                }
                
                // Superclass method list.
                Method meth = getMethodNoSuper_nolock(curClass, sel);
                if (meth) {
                    log_and_fill_cache(cls, meth->imp, sel, inst, curClass);
                    imp = meth->imp;
                    goto done;
                }
            }
    
        }
    

    分析代码可知,先从自身查找getMethodNoSuper_nolock如果查找到就返回并缓存该Method 通过 log_and_fill_cache.接下来通过For循环 一直查找父类,直到curClass == nil.如果没有查找到会进入下面这段代码

        // No implementation found. Try method resolver once.
        if (resolver  &&  !triedResolver) {
            runtimeLock.unlockRead();
            _class_resolveMethod(cls, sel, inst);
            runtimeLock.read();
            // Don't cache the result; we don't hold the lock so it may have 
            // changed already. Re-do the search from scratch instead.
            triedResolver = YES;
            goto retry;
        }
    

    _class_resolveMethod 来看看该函数实现

    /***********************************************************************
    * _class_resolveMethod
    * Call +resolveClassMethod or +resolveInstanceMethod.
    * Returns nothing; any result would be potentially out-of-date already.
    * Does not check if the method already exists.
    **********************************************************************/
    void _class_resolveMethod(Class cls, SEL sel, id inst)
    {
        if (! cls->isMetaClass()) {
            // try [cls resolveInstanceMethod:sel]
            _class_resolveInstanceMethod(cls, sel, inst);
        } 
        else {
            // try [nonMetaClass resolveClassMethod:sel]
            // and [cls resolveInstanceMethod:sel]
            _class_resolveClassMethod(cls, sel, inst);
            if (!lookUpImpOrNil(cls, sel, inst, 
                                NO/*initialize*/, YES/*cache*/, NO/*resolver*/)) 
            {
                _class_resolveInstanceMethod(cls, sel, inst);
            }
        }
    }
    static void _class_resolveInstanceMethod(Class cls, SEL sel, id inst)
    {
        if (! lookUpImpOrNil(cls->ISA(), SEL_resolveInstanceMethod, cls, 
                             NO/*initialize*/, YES/*cache*/, NO/*resolver*/)) 
        {
            // Resolver not implemented.
            return;
        }
    
        BOOL (*msg)(Class, SEL, SEL) = (__typeof__(msg))objc_msgSend;
        bool resolved = msg(cls, SEL_resolveInstanceMethod, 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(cls, sel, inst, 
                                 NO/*initialize*/, YES/*cache*/, NO/*resolver*/);
    
        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));
            }
        }
    }
        
    

    SEL_resolveInstanceMethod 接下来回到熟悉的OC上面来.

    相关文章

      网友评论

          本文标题:Runtime 复习(一)

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