iOS底层day7 - Runtime

什么是Runtime?

运行时,众所周知,Object-C是一门动态性比较强的语言,可以办到在程序运行中动态修改、添加方法等操作,而Runtime就是提供一套C语言的API,让开发者可以在程序运行时,做很多动态性操作。

探索objc_msgSend

我们知道,当我们调用一个方法时,实际上是给对象发送消息,底层调用的就是Runtime的API objc_msgSend,那么objc_msgSend底层最终的实现流程是怎么样的呢,让我们来窥探一下
objc_msgSend 分为三大阶段:

  • 消息发送阶段
  • 动态方法解析阶段
  • 消息转发阶段
    接下来,让我们一起来深入探索它的实现:
    objc源码中,我们搜索objc-msg-arm64,在文件内搜索objc_msgSend,我们可以找到它的汇编实现:
    ENTRY _objc_msgSend
    UNWIND _objc_msgSend, NoFrame
    MESSENGER_START

    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

这里入口,cmp x0 (x0:寄存器,存放receiver(消息接受者)) 判断传递进来的对象的非空判断,如果为空则跳转LNilOrTagged,如果不为空则进入下一步CacheLookup,我们搜索CacheLookup找到:

.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)

    // Clone scanning loop to miss instead of hang when cache is corrupt.
    // The slow path may detect any corruption and halt later.

    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:  // double wrap
    JumpMiss $0
    
.endmacro

看到注释,我们可以猜到它这里是通过@selector & mask 去 类对象的缓存列表中查找方法缓存,如果没有在缓存中查到到方法,则会走CheckMiss,我们搜索CheckMiss:

.macro CheckMiss
    // miss if bucket->sel == 0
.if $0 == GETIMP
    cbz x9, LGetImpMiss
.elseif $0 == NORMAL
    cbz x9, __objc_msgSend_uncached
.elseif $0 == LOOKUP
    cbz x9, __objc_msgLookup_uncached
.else
.abort oops
.endif
.endmacro

NORMAL下,它会调用__objc_msgSend_uncached,搜索__objc_msgSend_uncached找到:

    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

这里执行的是MethodTableLookup,搜索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,这里__class_lookupMethodAndLoadCache3的实现是由C语言实现的,在汇编中,方法名为__class_lookupMethodAndLoadCache3则在C语言中则会少一个下划线_class_lookupMethodAndLoadCache3,我们全局搜索_class_lookupMethodAndLoadCache3,我们在objc-runtime-new.mm中发现它的实现:

IMP _class_lookupMethodAndLoadCache3(id obj, SEL sel, Class cls)
{
    return lookUpImpOrForward(cls, sel, obj, 
                              YES/*initialize*/, NO/*cache*/, YES/*resolver*/);
}

它的实现:

IMP lookUpImpOrForward(Class cls, SEL sel, id inst, 
                       bool initialize, bool cache, bool resolver)
{
    IMP imp = nil;
    bool triedResolver = NO;

    runtimeLock.assertUnlocked();

    // Optimistic cache lookup
    if (cache) {
        imp = cache_getImp(cls, sel);
        if (imp) return imp;
    }

    // runtimeLock is held during isRealized and isInitialized checking
    // to prevent races against concurrent realization.

    // runtimeLock is held during method search to make
    // method-lookup + cache-fill atomic with respect to method addition.
    // Otherwise, a category could be added but ignored indefinitely because
    // the cache was re-filled with the old value after the cache flush on
    // behalf of the category.

    runtimeLock.read();

    if (!cls->isRealized()) {
        // Drop the read-lock and acquire the write-lock.
        // realizeClass() checks isRealized() again to prevent
        // a race while the lock is down.
        runtimeLock.unlockRead();
        runtimeLock.write();

        realizeClass(cls);

        runtimeLock.unlockWrite();
        runtimeLock.read();
    }

    if (initialize  &&  !cls->isInitialized()) {
        runtimeLock.unlockRead();
        _class_initialize (_class_getNonMetaClass(cls, inst));
        runtimeLock.read();
        // If sel == initialize, _class_initialize will send +initialize and 
        // then the messenger will send +initialize again after this 
        // procedure finishes. Of course, if this is not being called 
        // from the messenger then it won't happen. 2778172
    }

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

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

    // No implementation found, and method resolver didn't help. 
    // Use forwarding.

    imp = (IMP)_objc_msgForward_impcache;
    cache_fill(cls, sel, imp, inst);

 done:
    runtimeLock.unlockRead();

    return imp;
}

这里就是objc_msgSend的实现的核心代码
首先我们先看:

    if (cache) {
        imp = cache_getImp(cls, sel);
        if (imp) return imp;
    }

这里进行了一次判断缓存查找,由于我们在汇编中已经查找过一次缓存了,这里的cache传递进来则为NO,接下来,它又进行了一次缓存查找:

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

进入方法getMethodNoSuper_nolock:

getMethodNoSuper_nolock(Class cls, SEL sel)
{
    runtimeLock.assertLocked();

    assert(cls->isRealized());
    // fixme nil cls? 
    // fixme nil sel?

    for (auto mlists = cls->data()->methods.beginLists(), 
              end = cls->data()->methods.endLists(); 
         mlists != end;
         ++mlists)
    {
        method_t *m = search_method_list(*mlists, sel);
        if (m) return m;
    }

    return nil;
}

到这里,我们就可以看到它是用类对象找到datadata即为class_rw_t,从中找到method,这里它会循环method,执行search_method_list:

static method_t *search_method_list(const method_list_t *mlist, SEL sel)
{
    int methodListIsFixedUp = mlist->isFixedUp();
    int methodListHasExpectedSize = mlist->entsize() == sizeof(method_t);
    
    if (__builtin_expect(methodListIsFixedUp && methodListHasExpectedSize, 1)) {
        return findMethodInSortedMethodList(sel, mlist);
    } else {
        // Linear search of unsorted method list
        for (auto& meth : *mlist) {
            if (meth.name == sel) return &meth;
        }
    }

#if DEBUG
    // sanity-check negative results
    if (mlist->isFixedUp()) {
        for (auto& meth : *mlist) {
            if (meth.name == sel) {
                _objc_fatal("linear search worked when binary search did not");
            }
        }
    }
#endif

    return nil;
}

这里分情况进行了不同方式的查找方法,当发现列表是排序的则进行二分查找,其他情况下则线性循环查找方法,如果找到这个方法,它则执行log_and_fill_cache,将方法缓存在方法列表,然后取出该methodimp函数地址,执行 goto done:

 done:
    runtimeLock.unlockRead();

    return imp;

将函数的地址返回到汇编,执行
当没有找到method时,则会往下走:

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

这里循环获取该类的父类,也是进行了缓存查找方法列表查找,如果找到了则缓存在该类的缓存列表中,然后取出imp返回goto done 执行。
这里用一幅图来总结这个流程:

iOS底层day7 - Runtime_第1张图片
消息发送阶段

到这里就完成了objc_msgSend的第一步:消息发送阶段
接下来,我们来看消息动态解析阶段 :
当上面没有找到方法实现的时候则会走到以下代码:

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

这里,会进行一次动态方法解析,如果我们在类中实现了resolveInstanceMethod方法,则会调用到类的resolveInstanceMethod,我们可以在resolveInstanceMethod中动态添加方法实现,最终,会标记triedResolver已经动态解析了,并goto retry,重新进行一次消息发送
例如:

        MJStudent *student = [[MJStudent alloc] init];
        [student studentTestWithoutImpl];

这里的studentTestWithoutImpl并没有实现:

@implementation MJStudent
- (void)studentTest {
    NSLog(@"%s",__func__);
}

+ (BOOL)resolveInstanceMethod:(SEL)sel {
    
    if (sel == @selector(studentTestWithoutImpl)) {
        Method method = class_getInstanceMethod(self, @selector(studentTest));
        class_addMethod(self,
                        sel,
                        method_getImplementation(method),
                        method_getTypeEncoding(method));
        return YES;
    }
     return [super resolveInstanceMethod:sel];
}
@end

这里,当消息发送阶段走完后,没有找到方法实现,则会走到类的resolveInstanceMethod方法,我们在这里给类对象动态添加方法studentTestWithoutImpl的实现,实现方法就是studentTest,所以在外面调用studentTestWithoutImpl的时候,最终会走到studentTest
用一幅图来总结这个流程:

iOS底层day7 - Runtime_第2张图片
动态方法解析阶段

到这里,就完成了动态方法解析阶段
接下来我们来看消息转发阶段
当执行了动态方法解析后,triedResolver标记了已经动态解析,而又没找到对应的实现方法,则会重新goto retry,重新进行一次消消息发送阶段,然后又走到这里,发现已经标记了,就会执行到以下代码:

    // No implementation found, and method resolver didn't help. 
    // Use forwarding.

    imp = (IMP)_objc_msgForward_impcache;
    cache_fill(cls, sel, imp, inst);

由于_objc_msgForward_impcache是看不到它的实现的,这里直接指出它的底层实现:
首先,_objc_msgForward_impcache底层会先调用类的forwardingTargetForSelector方法,这个方法返回一个对戏那个,即可以处理这个消息的对象:

- (id)forwardingTargetForSelector:(SEL)aSelector {
    if (aSelector == @selector(studentTestWithoutImpl)) {
        return [[MJPig alloc] init];
    }
    return [super forwardingTargetForSelector:aSelector];
}

MJPig中实现studentTestWithoutImpl:

@implementation MJPig
- (void)studentTestWithoutImpl{
    NSLog(@"%s",__func__);
}
@end

即将studentTestWithoutImpl这条消息转发给了MJPig对象,最终则会调用MJPigstudentTestWithoutImpl方法,最终输出:

C9AC9CC7-14EE-41DB-A55F-BD3087A23FFF.png

当我们没有实现 forwardingTargetForSelector,它会走到下一步,执行 methodSignatureForSelector,返回封装了 type函数描述的对象 NSMethodSignature,当返回不为 时则会走到下一步执行 forwardInvocation,执行

- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
    if (aSelector == @selector(studentTestWithoutImpl)) {
        return [NSMethodSignature signatureWithObjCTypes:"v16@0:8"];
    }
    return [super methodSignatureForSelector:aSelector];
}

- (void)forwardInvocation:(NSInvocation *)anInvocation {
    return [anInvocation invokeWithTarget:[[MJPig alloc] init]];
}

用一幅图来描述这个流程:

iOS底层day7 - Runtime_第3张图片
消息转发流程

到这里, objc_msgSend的流程就结束了

Runtime 应用

一、动态创建一个类

        Class classDog = objc_allocateClassPair([NSObject class], "MJDog", 0);
        objc_registerClassPair(classDog);
        id dogInstance = [[classDog alloc] init];
        NSLog(@"%@",dogInstance);

这里声明MJDogNSObject的子类,并注册该类,最后创建对象
二、遍历成员变量

    unsigned int count;
    Ivar *ivars = class_copyIvarList([UITextField class], &count);
    for (int i = 0; i < count; i++) {
        // 取出i位置的成员变量
        Ivar ivar = ivars[I];
        NSLog(@"%s %s", ivar_getName(ivar), ivar_getTypeEncoding(ivar));
    }
    free(ivars);
+ (instancetype)mj_objectWithJson:(NSDictionary *)json
{
    id obj = [[self alloc] init];
    
    unsigned int count;
    Ivar *ivars = class_copyIvarList(self, &count);
    for (int i = 0; i < count; i++) {
        // 取出i位置的成员变量
        Ivar ivar = ivars[I];
        NSMutableString *name = [NSMutableString stringWithUTF8String:ivar_getName(ivar)];
        [name deleteCharactersInRange:NSMakeRange(0, 1)];
        
        // 设值
        id value = json[name];
        if ([name isEqualToString:@"ID"]) {
            value = json[@"id"];
        }
        [obj setValue:value forKey:name];
    }
    free(ivars);
    
    return obj;
}

三、方法应用


iOS底层day7 - Runtime_第4张图片
方法应用

总结

iOS底层day7 - Runtime_第5张图片
总结

你可能感兴趣的:(iOS底层day7 - Runtime)