Runtime

c c++ 汇编一起写成的api,为OC提供运行时。

官方文档

与运行时相对应的是编译时:源代码翻译成机器可识别的语言(汇编),最后翻译成二进制代码。

代码运行起来时,运行时会把可执行文件装载到内存中。

运行时版本:

 Legacy and Modern Versions

Objective-C 2.0 开始就是Modern版本的运行时

 

 

 old 和 new是为了兼容两个版本,现在主要用Modern版本。

 

 

 

在Main函数里面添加代码。打开终端

 

生成 Main.cpp

 

 

打开main.cpp  9万多行代码,前面是为了提供运行环境。

 

 

 

任何方法的调用,都会编译局成objc_msgSend

 RuntimePerson *person = ((RuntimePerson *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("RuntimePerson"), sel_registerName("new"));

 OC对象的本质就是结构体,方法的本质是发送消息。

 

 

 

 

 消息的组成(2个参数):

 

 

(id)objc_getClass("RuntimePerson") 消息接受者

sel_registerName("new") 方法编号

 

runtime三种调用方式:

runtime api

NSObject api isMemeber isClass

OC上层方法 @selector

这些对象,类,父类是如何发送消息的呢?

导入头文件

#import <objc/message.h>

((id (*)(idSEL ))objc_msgSend)(person, NSSelectorFromString(@"run")); //向对象发送消息

 

person 消息接受者

SEL方法编号

 

 ((id (*)(id, SEL ))objc_msgSend)(objc_getClass("RuntimePerson"), NSSelectorFromString(@"walk")); //向类发送消息

 

struct objc_super mySuper;

        mySuper.receiver = student;

        mySuper.super_class = class_getSuperclass([student class]);

        ((id (*)(id, SEL ))objc_msgSendSuper)((__bridge id)(&mySuper), @selector(run)); //向父类发送对象消息

 

struct objc_super myClassSuper;

       myClassSuper.receiver = [student class];

       myClassSuper.super_class = class_getSuperclass(object_getClass([student class]));

       ((id (*)(id, SEL ))objc_msgSendSuper)((__bridge id)(&myClassSuper), @selector(walk));

       //向父类发送类消息(类方法)

 

 

 

注意objc_msgSendSuper传的是结构体指针,是对结构体指针发送的run方法需要注意,结构体指针源码如下:

 

/// Specifies the superclass of an instance. 

struct objc_super {

    /// Specifies an instance of a class.

    __unsafe_unretained _Nonnull id receiver;

 

    /// Specifies the particular superclass of the instance to message. 

#if !defined(__cplusplus)  &&  !__OBJC2__   

    /* For compatibility with old objc-runtime.h header */

    __unsafe_unretained _Nonnull Class class; //如果不是objc2

#else

    __unsafe_unretained _Nonnull Class super_class; //如果是objc2

#endif

    /* super_class is the first class to search */

};

#endif

 

//对象方法存在哪里

//类方法存在哪

//类方法存在原类里面,以什么姿态存在?以实例方法是形式存在原类里面。

 

objc_msgSend  发送消息很快 快速(用汇编在缓存中查找)和慢速(通过C配合C++和汇编一起查找方法)两种方式

 

 

 

cache 存储SEL和IMP

方法查找的时候首先查找cache(哈希表),如果有直接返回。如果没有就通过c语言缓慢查找,如果找到了又会存到cache,方便下次快速查找。如果c语言也没找到,就会经过另外一个复杂的过程。

objc_msgSend为什么要用汇编写?

原因有两个:

1. c语言不可能通过写一个函数,保留未知的参数,跳转到任意的指针。(通过SEL,id,对象传进来,这些都是未知的,只有通过运行时才知道,c语言是无法实现的)

2. 速度快,oc要先转换成c或者c++而 c c++还需要编译,使用汇编可以直接操作寄存器,所以速度会更快

源码查找汇编代码:

 

_objc_msgSend

选择arm64架构

 

 

 

 

 

 

找到objc_msgSend汇编入口  ENTRY _objc_msgSend

分析源码:

UNWIND _objc_msgSend, NoFrame 窗口为0

 

 

 

tagged pointer是特殊的数据类型,比较小的数据类型例如NSDate类型,没有必要用64位数据去存储,所以使用tagged pointer去存储。

 

b.le LNilOrTagged 跳转到LNilOrTagged

 

 

 

b.eq LReturnZero 如果对象是nil,不需要发送消息,直接返回。

 

b.ne LGetIsaDone可以看出对isa做了相关处理。

 

 

结束消息发送

 

isa处理完毕后,会调用CacheLookup,NORMAl(缓存中找imp)normal为传的值 后面会出现两种情况直接调用imp或者调用 objc_msgSend_uncached(缓存中没有这个方法)

下面看下CacheLookup:

 

 

 有三种形式NORMAL, GETIMP, LOOKUP

.macro CacheLookup宏定义

 

1: cmp p9, p1 // if (bucket->sel != _cmd)

b.ne 2f //     scan more

CacheHit $0 // call or return imp

 

第一种情况:缓存找到,返回

 

2: // not hit: p12 = not-hit bucket

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

 

第二种情况:没找到

 

 

3: // wrap: p12 = first bucket, w11 = mask

add p12, p12, w11, UXTW #(1+PTRSHIFT)

                        // p12 = buckets + (mask << 1+PTRSHIFT)

 

// Clone scanning loop to miss instead of hang when cache is corrupt.

// The slow path may detect any corruption and halt later.

第三种情况:找到了,add imp到cache里面  

 

.macro CacheHit 宏定义

.if $0 == NORMAL

TailCallCachedImp x17, x12 // authenticate and call imp

如果为normal直接调用imp

 

 

.macro CheckMiss宏定义

如果找不到,并且传了NORMAL __objc_msgSend_uncached 会被调用

 

 

 

UNWIND __objc_msgSend_uncached, FrameWithNoSaves window没必要处理

 

MethodTableLookup 方法列表查找(核心方法)直接操作内存和寄存机非常快 比c快0.5倍到0.8倍

 

bl __class_lookupMethodAndLoadCache3 bl跳转__class_lookupMethodAndLoadCache3,在寻找这个方法的时候会发现找不到容易失去信心。

其实这里是跳转的c语言,需要去掉一个_ 搜索 _class_lookupMethodAndLoadCache3

 

 

name 方法名 imp 方法哈希表 name和imp是键值对

 

 

 这里的YES,NO,YES啥意思,initialize代表已经编译过了,在cache里面没有所以是NO,resolver是yes代表是否实现了这个类

 

下面是 c和c++查找方法的过程 lookUpImpOrForward方法源码:

/***********************************************************************

* lookUpImpOrForward.

* The standard IMP lookup. 

* initialize==NO tries to avoid +initialize (but sometimes fails)

* cache==NO skips optimistic unlocked lookup (but uses cache elsewhere)

* Most callers should use initialize==YES and cache==YES.

* inst is an instance of cls or a subclass thereof, or nil if none is known. 

*   If cls is an un-initialized metaclass then a non-nil inst is faster.

* May return _objc_msgForward_impcache. IMPs destined for external use 

*   must be converted to _objc_msgForward or _objc_msgForward_stret.

*   If you don't want forwarding at all, use lookUpImpOrNil() instead.

**********************************************************************/

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) { // 可能有缓存 -- NO

        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.lock();

    checkIsKnownClass(cls); 判断是否已经声明,或是是一个已知的类,如果是未知的类会报出相应的错误信息

 

    //

    if (!cls->isRealized()) { //如果是已知的类,判断是否已经实现了类,如果没有实现就去实现,给相关的内部DATA赋值

        // DATA

        realizeClass(cls);

    }

 

    if (initialize  &&  !cls->isInitialized()) { //如果需要初始化,并且没有有初始化则进行类初始化。

        runtimeLock.unlock();

        _class_initialize (_class_getNonMetaClass(cls, inst));

        runtimeLock.lock();

        // 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.assertLocked();

 

    // Try this class's cache.

    // 这里为什么又重新去取imp ? 为什么这样设计?

    // 1. 并发,多线程,资源抢夺,已经更新了cahce

    // remap(cls)这个方法在加载类的手会重映射,可能就有了方法实现,所以要在这里重新cache_getImp

    imp = cache_getImp(cls, sel);

    if (imp) goto done;

 

    // Try this class's method lists. 

    { //首先在自己本类查找是否有相关方法sel

        Method meth = getMethodNoSuper_nolock(cls, sel); //后面有这个方法的分析

        if (meth) {

            log_and_fill_cache(cls, meth->imp, sel, inst, cls); 找到之后直接缓存赋值,下次直接总cache里面汇编查找(CacheHit)。

            imp = meth->imp;

            goto done;

        }

    }

   如果从本类里面没有查找到相关方法,那么就查找父类里面是否有相关实现

    // Try superclass caches and method lists.

    {

        unsigned attempts = unreasonableClassCount();

        for (Class curClass = cls->superclass;

             curClass != nil;

             curClass = curClass->superclass) 直到curClass为nil,因为NSObject的superClass 为nil,所以最终直到NSObject

        {

            // 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); 从父类cache里面查找imp

            if (imp) { 如果找到了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. 如果没有找到imp实现,就会尝试动态方法解析执行一次

    // 类方法的查找 -- 汇编

    // 找父类方法 没有

    // 动态方法解析

    // 类的类方法 = 元类实例方法  -(void)run{} -- NSObject

    // +(void)run{}

    

    if (resolver  &&  !triedResolver) {

        runtimeLock.unlock();

        _class_resolveMethod(cls, sel, inst); //后面有源码解析

        runtimeLock.lock();

        // 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.unlock();

 

    return imp;

}

 

首先判断了cahce是否有缓存,如果传了YES会走这个汇编方法,快速查找方法

 

 

 

查找本类里里面的sel方法:

static method_t *

getMethodNoSuper_nolock(Class cls, SEL sel)

{

    runtimeLock.assertLocked();

 

    assert(cls->isRealized());

    // fixme nil cls? 

    // fixme nil sel?

 

  //从begin到end遍历查找method,找到返回,没有找到返回nil

    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;

}

 

总结:思维导图

 

 

 

如果汇编和c c++都没有找到方法,那么会走方法动态解析的流程:

/***********************************************************************

* _class_resolveMethod

* Call +resolveClassMethod or +resolveInstanceMethod. 如果方法没有实现就会调用resolveClassMethod(类方法动态解析),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]

        // LGPERSON(类方法) - 元类(实例) - 根元类(实例) -- NSObject (实例方法)

        _class_resolveClassMethod(cls, sel, inst);

        if (!lookUpImpOrNil(cls, sel, inst, 

                            NO/*initialize*/, YES/*cache*/, NO/*resolver*/)) 

        {

            _class_resolveInstanceMethod(cls, sel, inst);

        }

    }

}

 

 

经过尝试,有效,这里出现了一个新的问题就是动态方法解析调用了两次

 

 

 

 

 

 

 第一次发送是在_class_resolveMethod 方法里面通过下面的代码发送第一次消息:

 BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;

 bool resolved = msg(cls, SEL_resolveInstanceMethod, sel);

 

 第二次 从下面的截图可以发现是在forwarding消息转发的时候又调用了一次resolveClassMethod 所以一共调用两次动态解析方法

消息转发流程:在”消息无法处理“的时候又会调用一次resolveINstanceMethod

 

 下面在动态解析方法里面处理添加先未实现的方法:

 

#import "RuntimePerson.h"

 

@implementation RuntimePerson

 

//-(void)run{

//    NSLog(@"%s", __func__);

//    NSLog(@"RuntimePerson -- %s", __func__);

//}

 

//+(void)walk{

//     NSLog(@"RuntimePerson -- %s", __func__);

//}

 

-(void)readBook {

    NSLog(@"读书");

}

 

+(void)helloword {

    NSLog(@"helloword");

}

 

#pragma mark - 动态方法解析

 

+ (BOOL)resolveInstanceMethod:(SEL)sel{

    if(sel == @selector(run)) {

        NSLog(@"对象方法解析走这里");

        SEL readSEL = @selector(readBook);

        Method readM = class_getInstanceMethod(self, readSEL);

        IMP readImp = method_getImplementation(readM);

        const char *type = method_getTypeEncoding(readM);

        return class_addMethod(self, sel, readImp, type);

    }

    return [super resolveInstanceMethod:sel];

}

 

+ (BOOL)resolveClassMethod:(SEL)sel{

//    NSLog(@"来了 老弟 %s", __func__);

    

    if(sel == @selector(walk)) {

        NSLog(@"类方法解析走这里");

        SEL hellowordSEL = @selector(helloword);

        //类方法就存在我们的原类的方法列表

        Method hellowordM1 = class_getClassMethod(self, hellowordSEL);

        Method hellowordM = class_getInstanceMethod(object_getClass(self), hellowordSEL);

        IMP hellowordImp = method_getImplementation(hellowordM);

        const char *type = method_getTypeEncoding(hellowordM);

        NSLog(@"%s", type);

        return class_addMethod(object_getClass(self), sel, hellowordImp, type);

    }

    return  [super resolveClassMethod:sel];

}

@end

这里发现一个问题,类的类方法和元类的对象方法指针地址是相同的,所以是同一个东西。

 

 

总结:在

 

 这里有个问题就是为什么会再次调用_class_resolveInstanceMethod

 

 

 

 

 

 

 

 

 如果动态方法决议没有成功后面会继续走下层处理(消息转发):

 

 

 

////对象方法转发

- (id)forwardingTargetForSelector:(SEL)aSelector{

    NSLog(@"%s",__func__);

    if (aSelector == @selector(run)) {

        // 转发给我们的LGStudent 对象

        return [RuntimeDog new];

    }

    return [super forwardingTargetForSelector:aSelector];

}

 

//类方法转发流程  如果这里返回YES,就不会走后面两个方法了

+ (id)forwardingTargetForSelector:(SEL)aSelector {  在这里可以做一系列自定义处理和crash收集,还可以防止奔溃

    NSLog(@"%s", __func__);

    return [super forwardingTargetForSelector:aSelector];

}

 

+ (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{

    NSLog(@"%s",__func__);

    if (aSelector == @selector(walk)) {

        return [NSMethodSignature signatureWithObjCTypes:"v@:@"];

    }

    return [super methodSignatureForSelector:aSelector];

}

 

+ (void)forwardInvocation:(NSInvocation *)anInvocation{ 消息转发

    NSLog(@"%s",__func__);

    

 

  可以在这里打印系统调用堆栈,收集起来,发送给服务器,bugly 友盟等第三方的做法

 

  另外切面编程(aspect)也会用到这里的消息转发

 

  或者是数组字典的一些边界和非空校验也可能会用到消息转发

 

  //转发给person的readBook方法

    NSString *sto = @"奔跑少年";

    anInvocation.target = [RuntimePerson class];

//    [anInvocation setArgument:&sto atIndex:2];

    NSLog(@"%@",anInvocation.methodSignature);

    anInvocation.selector = @selector(readBook); 

    [anInvocation invoke];

    

}

调用顺序

forwardingTargetForSelector -> methodSignatureForSelector(方法签名) -> forwardInvocation(消息转发)

 

下面探索下源码:

   imp = (IMP)_objc_msgForward_impcache;

    cache_fill(cls, sel, imp, inst);

 _objc_msgForward_impcache这部分是汇编,需要前面加个_搜索

 

跳转到 __objc_msgForward

 

 

__objc_forward_handler 回调信息,这里就是我们平时方法没有实现报错信息的打印。

 

 

 

 

 

 

 

beq __objc_msgForward 跳转到了__objc_msgForward 方法

 

动态方法解析只有汇编调用,没有源码实现 (是闭源的)

这里使用系统提供的一个函数instrumentObjcMessageSends(一般在系统内部使用) 可以打印所有的调用信息,保存到一个文件中/private/tmp在这个路径下面 

 

在mac APP项目中可以生成,但是在iOS app工程中无法生成(具体原因还不清楚)

 

 

 

 

 

 

 

 

 

 

 

posted @ 2020-01-01 17:09  do+better  阅读(322)  评论(0编辑  收藏  举报