iOS runtime 部分三

2020-07-13  本文已影响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. 流程图总结消息发送, 动态解析, 消息转发的过程


文中测试代码


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

上一篇下一篇

猜你喜欢

热点阅读