美文网首页
iOS runtime 部分三

iOS runtime 部分三

作者: 飞不越疯人院 | 来源:发表于2020-07-13 22:28 被阅读0次

    主要探究 objc_msgSend()的流程;

    文中使用的 objc4源码是objc-781版本;

    runtime 部分一
    runtime 部分二
    runtime 部分三


    1. objc_msgSend()的流程;

    首先我们都知道, 我们调用方法后到底层就是通过objc_msgSend()来实现的, 这个机制就是我们所说的消息发送机制, 这个过程分为消息发送 动态解析 消息转发三个阶段;
    转化为objc_msgSend()有两个参数, 第一个是接收者receiver, 第二个是SEL;

    ///不论是类方法还是实例方法底层都是objc_msgSend()方式实现;
    Person *person = [[Person alloc] init];
    [person realizedMehod];
    ///转化为C++后代码如下
    Person *person = ((Person *(*)(id, SEL))(void *)objc_msgSend)((id)((Person *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("Person"), sel_registerName("alloc")), sel_registerName("init"));
    ((void (*)(id, SEL))(void *)objc_msgSend)((id)person, sel_registerName("realizedMehod"));
    

    由于 objc_msgSend()的实现过程是通过汇编实现, 本人能力有限, 只能大致推测下, 从 objc_msgSend()开始的整个流程, 如果有;理解错误的地方还请不吝赐教;
    注意汇编中调用 C 或者 C++的函数会在函数名之前加上_, 例如objc_msgSend()在汇编中对应的就是_objc_msgSend;

    ///入口
    /********************************************************************
     *
     * id objc_msgSend(id self, SEL _cmd, ...);
     * IMP objc_msgLookup(id self, SEL _cmd, ...);
     * 
     * objc_msgLookup ABI:
     * IMP returned in x17
     * x16 reserved for our use but not used
     *
     ********************************************************************/
    ....
            //_objc_msgSend开始
        ENTRY _objc_msgSend
        UNWIND _objc_msgSend, NoFrame
        cmp p0, #0          // nil check and tagged pointer check
    ///在 arm64架构下SUPPORT_TAGGED_POINTERS = 1 , 所以下一步LNilOrTagged, 检查 receiver 是否为空;
    #if SUPPORT_TAGGED_POINTERS
        b.le    LNilOrTagged        //  (MSB tagged pointer looks negative)
    #else
        b.eq    LReturnZero
    #endif
        ldr p13, [x0]       // p13 = isa
            ///通过 isa 获取 class
        GetClassFromIsa_p16 p13     // p16 = class
    LGetIsaDone:
        // calls imp or objc_msgSend_uncached
            ///开始缓存查找, 注意入参是NORMAL
        CacheLookup NORMAL, _objc_msgSend
    
    #if SUPPORT_TAGGED_POINTERS
    LNilOrTagged:
        b.eq    LReturnZero     // nil check
    
        // tagged
        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]
        adrp    x10, _OBJC_CLASS_$___NSUnrecognizedTaggedPointer@PAGE
        add x10, x10, _OBJC_CLASS_$___NSUnrecognizedTaggedPointer@PAGEOFF
        cmp x10, x16
        b.ne    LGetIsaDone
    
        // 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
    // 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
    
    ===>
    ///缓存查找
    .macro CacheLookup
    LLookupStart$1:
        // p1 = SEL, p16 = isa
        ldr p11, [x16, #CACHE]              // p11 = mask|buckets
    #if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16
            /*
              arm64架构执行这里的逻辑;
              虽然我们不知道汇编的具体作用, 但是从注释依然可以看出一些信息. 从散列表(buckets)中
              通过_cmd & mask来获取方法 IMP, 这个跟之前的方法缓存的算法和过程相对应;
            */
        and p10, p11, #0x0000ffffffffffff   // p10 = buckets
        and p12, p1, p11, LSR #48       // x12 = _cmd & mask
    #elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_LOW_4
        and p10, p11, #~0xf         // p10 = buckets
        and p11, p11, #0xf          // p11 = maskShift
        mov p12, #0xffff
        lsr p11, p12, p11               // p11 = mask = 0xffff >> p11
        and p12, p1, p11                // x12 = _cmd & mask
    #else
    
    ...
    
    #if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16
        add p12, p12, p11, LSR #(48 - (1+PTRSHIFT))
                        // p12 = buckets + (mask << 1+PTRSHIFT)
    #elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_LOW_4
        add p12, p12, p11, LSL #(1+PTRSHIFT)
                        // p12 = buckets + (mask << 1+PTRSHIFT)
    #else
    #error Unsupported cache mask storage for ARM64.
    #endif
    
        // Clone scanning loop to miss instead of hang when cache is corrupt.
        // The slow path may detect any corruption and halt later.
    
        ldp p17, p9, [x12]      // {imp, sel} = *bucket
    1:  cmp p9, p1          // if (bucket->sel != _cmd)
        b.ne    2f          //     scan more
          ///找到缓存 call or return imp
        CacheHit $0         // call or return imp
        
    2:  // not hit: p12 = not-hit bucket
          ///缓存没有找到,  调用CheckMiss
        CheckMiss $0            // miss if bucket->sel == 0
        cmp p12, p10        // wrap if bucket == buckets
        b.eq    3f
        ldp p17, p9, [x12, #-BUCKET_SIZE]!  // {imp, sel} = *--bucket
        b   1b          // loop
    
    LLookupEnd$1:
    LLookupRecover$1:
    3:  // double wrap
        JumpMiss $0
    
    .endmacro
    
    ===>
    ///宏定义CheckMiss
    .macro CheckMiss
        // miss if bucket->sel == 0
    .if $0 == GETIMP
        cbz p9, LGetImpMiss
    ///之前的入参是NORMAL, 下一步调用__objc_msgSend_uncached
    .elseif $0 == NORMAL
        cbz p9, __objc_msgSend_uncached
    .elseif $0 == LOOKUP
        cbz p9, __objc_msgLookup_uncached
    .else
    .abort oops
    .endif
    .endmacro
    
    ===>
    ///__objc_msgSend_uncached的流程
    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
    MethodTableLookup
    TailCallFunctionPointer x17
    END_ENTRY __objc_msgSend_uncached
    
    ===>
    
    .macro MethodTableLookup
    ...
    // lookUpImpOrForward(obj, sel, cls, LOOKUP_INITIALIZE | LOOKUP_RESOLVER)
    // receiver and selector already in x0 and x1
    mov x2, x16
    mov x3, #3
    ///调用lookUpImpOrForward方法
    bl  _lookUpImpOrForward
    ...
    
    下面就是到源码lookUpImpOrForward的实现;
    ===>
    
    //**********************************************************************************************//
    在arm64-asm.h文件中可以得知在 arm64 & __LP64__模式下SUPPORT_TAGGED_POINTERS = 1
    #if __arm64__
    #if __LP64__
    // true arm64
    #define SUPPORT_TAGGED_POINTERS 1
    ....
    ===
    在objc-config.h文件中我们可以得知在 arm64 &  __LP64__模式下 CACHE_MASK_STORAGE = CACHE_MASK_STORAGE_HIGH_1
    #define CACHE_MASK_STORAGE_OUTLINED 1
    #define CACHE_MASK_STORAGE_HIGH_16 2
    #define CACHE_MASK_STORAGE_LOW_4 3
    #if defined(__arm64__) && __LP64__
    #define CACHE_MASK_STORAGE CACHE_MASK_STORAGE_HIGH_16
    #elif defined(__arm64__) && !__LP64__
    #define CACHE_MASK_STORAGE CACHE_MASK_STORAGE_LOW_4
    #else
    #define CACHE_MASK_STORAGE CACHE_MASK_STORAGE_OUTLINED
    #endif
    #endif
    //**********************************************************************************************//
    
    

    上面的过程主要是记录下在汇编层面objc_msgSend()的流程, 下面着重说下lookUpImpOrForward的实现;

    /***********************************************************************
    * lookUpImpOrForward.
    * The standard IMP lookup. 
    标准的 IMP 查找方法
    ...
    *   If you don't want forwarding at all, use LOOKUP_NIL.
    注意这句话, 如果你完全不想使用消息转发, 则使用LOOKUP_NIL
    **********************************************************************/
    IMP lookUpImpOrForward(id inst, SEL sel, Class cls, int behavior)
    {
        ///获取消息转发的 IMP
        const IMP forward_imp = (IMP)_objc_msgForward_impcache;
         ///初始的imp为空
        IMP imp = nil;
        ///当前类
        Class curClass;
        ///runtime锁
        runtimeLock.assertUnlocked();
    
        // Optimistic cache lookup
          ///查找方法缓存, 如果查找到缓存直接结束流程将方法返回
        if (fastpath(behavior & LOOKUP_CACHE)) {
            imp = cache_getImp(cls, sel);
            if (imp) goto done_nolock;
        }  
        ///检查类的initialize相关
         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;
    
        ///核心方法, 通过向上遍历当前类的父类, 来查找imp
        for (unsigned attempts = unreasonableClassCount();;) {
            // curClass method list.
            ///获取当前类的方法列表, 不查询父类
            Method meth = getMethodNoSuper_nolock(curClass, sel);
            ///如果当前类中查找到imp则结束查找 去done:出缓存imp然后返回这个imp;
            if (meth) {
                imp = meth->imp;
                goto done;
            }
             /*
              注意这个地方的写法, 执行完这句代码后, curClass就已经指向父类了;
              if (slowpath((curClass = curClass->superclass) == nil)) {}
              等同于下面两句语句组合, 如果有疑问可以自行测试下;
              curClass = curClass->superclass
              if (slowpath(curClass  == nil) {}
            
            如果查询到基类仍然没有查找大相关方法, 则使用消息转发(注意这里只是跳出循环)
            实际上下面要先判断动态解析, 动态解析是先于消息转发的;
             */
            if (slowpath((curClass = curClass->superclass) == 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.
            /*
            从curClass已经指向了父类, 所以这里判断父类中是否有消息转发, 
            如果子类没有消息转发相关处理, 写在父类中实现消息转发也会有效;
            仍然是跳出循环, 先去动态解析;
            */
            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;
            }  
            //如果父类中方法缓存中查找到了, 则将方法缓存到本类然后返回imp
            if (fastpath(imp)) {
                // Found the method in a superclass. Cache it in this class.
                goto done;
            }
        }
    
        // No implementation found. Try method resolver once.
        /*  
          如果没有找到IMP则开始动态解析, 不论是否成功最后都会再次调用lookUpImpOrForward;
          注意只进行一次动态解析, 如果动态解析成功, 则将相关方法缓存到本类;
          下次再次调用时则是直接查找类中方法列表即可;
          如果动态解析失败, 则再次lookUpImpOrForward, 重新开始流程, 进入消息转发阶段;
          具体请看resolveMethod_locked的官方注释和实现流程;
         */
        if (slowpath(behavior & LOOKUP_RESOLVER)) {
            behavior ^= LOOKUP_RESOLVER;
            return resolveMethod_locked(inst, sel, cls, behavior);
        }
    
     done:
        ///将查找到的imp缓存
        log_and_fill_cache(cls, imp, sel, inst, curClass);
        runtimeLock.unlock();
     done_nolock:
        ///如果完全不想使用消息转发, 但是获取到的缓存imp=forward_imp则直接返回nil
        if (slowpath((behavior & LOOKUP_NIL) && imp == forward_imp)) {
            return nil;
        }
        ///最终返回imp
        return imp;
    }
    
    
    //******************************************************************//
    ///动态解析方法入口
    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 (!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);
    }
    ===>
    ///以实例方法为例, 动态解析方法入口中会调用  resolveInstanceMethod方法
    
    /***********************************************************************
    * resolveInstanceMethod
    * Call +resolveInstanceMethod, looking for a method to be added to class cls.
       ///寻找要添加到类中的method;
    * cls may be a metaclass or a non-meta class.
    * Does not check if the method already exists.
    **********************************************************************/
    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);
     ...
    }
    
    

    至此objc_msgSend()的流程大致结束, 下面代码测试下动态解析和消息转发;

    2. 动态解析

    两个关键方法: 实例方法+(BOOL)resolveInstanceMethod: ; 类方法+(BOOL)resolveClassMethod:
    以实例方法为例, 简单的理解为:
    调用notRealizeMethod方法后后, 如果本类或者父类实现了这个方法则调用, 否则检查是否实现动态解析方法,
    实现了resolveInstanceMethod方法,开始动态解析; 这个地方就是动态为本类添加一个方法去映射实现notRealizeMethod;
    没有实现resolveInstanceMethod, 程序crash抛出unrecognized selector sent to instance;
    开始动态解析;

    调用未实现的方法notRealizeMethod
    Cat *cat = [[Cat alloc] init];
    [cat notRealizeMethod];
    ///Cat的实现如下;
    //.h文件
    #import <Foundation/Foundation.h>
    NS_ASSUME_NONNULL_BEGIN
    @interface Cat : NSObject
    ///此方法未实现
    - (void)notRealizeMethod;
    @end
    NS_ASSUME_NONNULL_END
    ///.m文件为
    #import "Cat.h"
    #import <objc/runtime.h>
    @implementation Cat
    - (void)HandleNotRealizedMethod {
        NSLog(@"HandleNotRealizedMethod : %s", __func__);
    }
    /*
     对notRealizeMethod方法, 没有实现, 调用后;
     实现了resolveInstanceMethod方法,开始动态解析;
     没有实现resolveInstanceMethod, 程序crash抛出unrecognized selector sent to instance;
     开始动态解析
     */
    + (BOOL)resolveInstanceMethod:(SEL)sel {
        if (sel  == @selector(notRealizeMethod)) {
            SEL handelSel = @selector(HandleNotRealizedMethod);
            Method handleMethod = class_getInstanceMethod(self, handelSel);
            IMP  imp  = class_getMethodImplementation(self, handelSel);
            /*
             为某个类添加Method;
             参数1: 为哪个类添加方法;
             参数2: 为哪个方法添加实现;
             参数3: 方法的具体实现;
             参数4: 方法的编码格式;
             */
            class_addMethod(self, sel, imp, method_getTypeEncoding(handleMethod));
            return YES;
        }
        return [super resolveInstanceMethod:sel];
    }
    @end
    ///运行后的结果为
    2020-07-04 17:36:59.568045+0800 objc_msgSend[11787:194063] HandleNotRealizedMethod : -[Cat HandleNotRealizedMethod]
    

    3. 消息转发

    如果动态解析方法没有实现, 或者没有处理动态解析, 则进入消息转发阶段;
    以实例方法为例, 大致流程为:
    几个主要的方法;
    - (id)forwardingTargetForSelector:()返回一个能处理notRealizeInstanceMethod的对象;
    - (NSMethodSignature *)methodSignatureForSelector:()方法签名;
    - (void)forwardInvocation:()最终的处理

    注意: 类方法也有消息转发;
    把相关的方法打出后手动改为+即可;处理的流程跟实例方法类似;
    + (id)forwardingTargetForSelector:();
    + (NSMethodSignature *)methodSignatureForSelector:();
    + (void)forwardInvocation:();

    测试代码

    ///调用方法
    Pig *pig = [[Pig alloc] init];
    [pig notRealizeInstanceMethod];
    [Pig notRealizeClassMethod];
       
    
    ///动态解析阶段, 不处理或者处理不成功, 进入消息转发阶段
    + (BOOL)resolveInstanceMethod:(SEL)sel {
        return [super resolveInstanceMethod:sel];
    }
    
    /*
     aSelector这个时候就是notRealizeInstanceMethod;
     这个地方需要返回一个值, 就是返回一个能处理notRealizeInstanceMethod的对象;
     例如Piggy也有实例方法notRealizeInstanceMethod, 并且也实现了,这时可以返回Piggy的实例对象;即可处理方法;
     */
    - (id)forwardingTargetForSelector:(SEL)aSelector {
    //如过将下面注释的代码打开, 则直接将消息转发给Piggy的实例对象, 方法签名的相关方法不再生效;
        ///如果发现Pig对象调用notRealizeInstanceMethod方法, 方法没有实现, 并且动态解析失败, 则将消息转发给Piggy对象;  
    //    if (aSelector == @selector(notRealizeInstanceMethod)) {
    //        Piggy *piggy =  [[Piggy alloc] init];
    //        /*
    //         消息转发的过程, 源码不开源, 并不能找到相关流程, 但是消息转发后有做一个操作就是objc_msgSend( piggy,  aSelector);
    //         就是让piggy调用aSelector;
    //         */
    //        return piggy;
    //    }
        return [super forwardingTargetForSelector:aSelector];
    }
    
    
    /*
     如果forwardingTargetForSelector并不能返回一个有效的对象; 开始进入方法签名阶段
     */
    - (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
        /*
         方法编码, 要返回aSelector的编码格式;
         如果返回一个合理的值, 则调用forwardInvocation方法;
         */
        NSMethodSignature *sig = [NSMethodSignature signatureWithObjCTypes:"v16@0:8"];
        return sig;
    }
    
    
    /*
     到了这一步, 可以做任何操作了
     类似KVC是实现了setValue: forUndefinedKey:即使没有相应key, 实现了此方法, 什么都不做也不会崩溃;
     
     NSInvocation封装了一个方法的调用信息; 调用者, 方法, 方法编码;
     target: 方法之前的调用者, 可更改为其他调用者;
     selector : 需要调用的方法, 可更改为其他方法;
     methodSignature : 方法的签名信息; 不可更改;
    
     只要target和selector是配套合理的,methodSignature可以忽略;例如:
     anInvocation.target = [Dog class];
     anInvocation.selector = @selector(classTest:);
     [anInvocation invoke];
     */
    -  (void)forwardInvocation:(NSInvocation *)anInvocation {
        anInvocation.target = [Dog class];
        anInvocation.selector = @selector(classTest:);
        [anInvocation invoke];
     }
    

    以类方法为例验证另一个问题: 子类中调用没有实现方法, 且没有做方法的消息转发, 但是父类实现了消息转发, 也会有效;

    //调用方法
    [Pig notRealizeClassMethod];
    
    ///Pig的父类Animal的.m实现为
    #import "Animal.h"
    @implementation Animal
    +  (id)forwardingTargetForSelector:(SEL)aSelector {
        return [super forwardingTargetForSelector:aSelector];
    }
    +  (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
        NSMethodSignature *sig = [NSMethodSignature signatureWithObjCTypes:"v16@0:8"];
        return sig;
    }
    + (void)forwardInvocation:(NSInvocation *)anInvocation {
        NSLog(@"Pig类中没有实现notRealizeClassMethod方法, 且没有做类方法的消息转发, 但是父类实现了消息转发, 也会有效;");
    }
    @end
    
    2020-07-04 18:16:20.022633+0800 objc_msgSend[15196:261753] Pig类中没有实现notRealizeClassMethod方法, 
    且没有做类方法的消息转发, 但是父类实现了消息转发, 也会有效;
    
    

    4. 流程图总结消息发送, 动态解析, 消息转发的过程

    • 消息发送阶段的流程:


    • 动态解析阶段流程:


      image.png
    • 消息转发流程
      类方法的处理流程类似, 把相关的-号方法换成+号方法;


    文中测试代码


    参考文章和下载链接
    Apple 一些源码的下载地址
    方法的查找顺序
    什么是散列表
    LP64 结构数据占据多少位
    LP64什么意思
    汇编和 C 函数的相互调用
    iOS 方法签名机制
    iOS方法返回值和参数对应的Type Encodings

    相关文章

      网友评论

          本文标题:iOS runtime 部分三

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