zoukankan      html  css  js  c++  java
  • 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工程中无法生成(具体原因还不清楚)

     

     

  • 相关阅读:
    基本数据结构:链表(list)
    字符串函数
    TCHAR
    开源库链接
    视频 链接
    tabbar
    加密
    安全类链接,https
    资本
    审核 -链接 - 发布证书
  • 原文地址:https://www.cnblogs.com/coolcold/p/12129285.html
Copyright © 2011-2022 走看看