Runtime(一)

作者: 二猪哥 | 来源:发表于2020-02-29 11:32 被阅读0次

    一、Runtime 是什么

    首先我们都知道,将源代码转换为可执行的程序,通常要经过三个步骤:编译、链接、运行。不同的编译语言,在这三个步骤中所进行的操作又有些不同。
    比如:

    • C 语言 作为一门静态类语言,在编译阶段就已经确定了所有变量的数据类型,同时也确定好了要调用的函数,以及函数的实现。

    • 而 Objective-C 语言 是一门动态语言。在编译阶段并不知道变量的具体数据类型,也不知道所真正调用的哪个函数。只有在运行时间才检查变量的数据类型,同时在运行时才会根据函数名查找要调用的具体函数。这样在程序没运行的时候,我们并不知道调用一个方法具体会发生什么。所以只要声明了函数,即使没有实现也不会报错。

    • Objective-C 语言 把一些决定性的工作从编译阶段、链接阶段推迟到 运行时阶段 的机制,使得 Objective-C 变得更加灵活。我们甚至可以在程序运行的时候,动态的去修改一个方法的实现,这也为大为流行的『热更新』提供了可能性。

    • 而实现 Objective-C 语言 运行时机制 的一切基础就是 Runtime。

    • 因此Runtime 实际上是一套接口都是C语言,源码由C\C++\汇编语言编写的API,OC代码最终都会被编译器转化为运行时代码,这个API使我们可以在程序运行时通过消息机制动态的创建对象、检查对象,修改类和对象的方法。

    二、Runtime底层原理解析(使用源码:objc4-750

    (一)、isa详解

    要想学习Runtime,首先要了解它底层的一些常用数据结构,比如isa指针;在arm64架构之前,isa就是一个普通的指针,存储着Class、Meta-Class对象的内存地址;从arm64架构开始,对isa进行了优化,变成了一个共用体(union)结构,还使用位域来存储更多的信息。

    查看源码共用体结构:全局搜索:objc_object { ——>objc-private.h(选择这个文件)——>isa_t isa——> isa_t——>union isa_t {}。

    union isa_t {
        isa_t() { }
        isa_t(uintptr_t value) : bits(value) { }
    
        Class cls;
        uintptr_t bits;
    #if defined(ISA_BITFIELD)
        struct {
            ISA_BITFIELD;  // defined in isa.h
        };
    #endif
    };
    
    /*ISA_BITFIELD表示如下*/
     uintptr_t nonpointer        : 1;   //位域   nonpointer:代表二进制的1位。
     uintptr_t has_assoc         : 1;
     uintptr_t has_cxx_dtor      : 1;
     uintptr_t shiftcls          : 33; /*MACH_VM_MAX_ADDRESS 0x7fffffe00000*/
     uintptr_t magic             : 6;
     uintptr_t weakly_referenced : 1;
     uintptr_t deallocating      : 1;
     uintptr_t has_sidetable_rc  : 1;
     uintptr_t extra_rc          : 19;
    
    
    //最终:
    union isa_t {
        Class cls;
        uintptr_t bits;
        struct {
            uintptr_t nonpointer        : 1;
            uintptr_t has_assoc         : 1;
            uintptr_t has_cxx_dtor      : 1;
            uintptr_t shiftcls          : 33; /*MACH_VM_MAX_ADDRESS 0x7fffffe00000*/
            uintptr_t magic             : 6;
            uintptr_t weakly_referenced : 1;
            uintptr_t deallocating      : 1;
            uintptr_t has_sidetable_rc  : 1;
            uintptr_t extra_rc          : 19;
        };
    };
    
    (1)、位域

    前面提到了在arm64之后isa优化成了共用体,那么要理解这个共用体就不得不先了解位域,因为在这个共用体中,是使用位域去存储信息的。

    • 什么是位域

    有些数据在存储时并不需要占用一个完整的字节,只需要占用一个或几个二进制位即可。例如开关只有通电和断电两种状态,用0和1表示足以,也就是用一个二进位。正是基于这种考虑,C语言又提供了一种数据结构,叫做“位域”或“位段”

    位域是操控位的一种方法(操控位的另一种方法是使用按位运算符,按位运算符将在之后的笔记中做介绍)。

    位域通过一个结构声明来建立:该结构声明为每个字段提供标签,并确定该字段的宽度。例如,下面的声明:

    struct {
        uintptr_t nonpointer        : 1;
        uintptr_t has_assoc         : 1;
        uintptr_t has_cxx_dtor      : 1;
        uintptr_t shiftcls          : 44; /*MACH_VM_MAX_ADDRESS 0x7fffffe00000*/
        uintptr_t magic             : 6;
        uintptr_t weakly_referenced : 1;
        uintptr_t deallocating      : 1;
        uintptr_t has_sidetable_rc  : 1;
        uintptr_t extra_rc          : 8;
    };
    

    深入了解位域的可看这里。;理解位域之前,我们先来看一下位运算,位运算是操控位的另一种方法。

    • 位运算:话不多说,直接上代码:(通过位运算添加属性)
      MHYPerson类:
    MHYPerson.h    声明tall、rich、handsome三个属性的set、get方法。
    #import <Foundation/Foundation.h>
    
    @interface MHYPerson : NSObject
    
    - (void)setTall:(BOOL)tall;
    - (void)setRich:(BOOL)rich;
    - (void)setHandsome:(BOOL)handsome;
    
    - (BOOL)isTall;
    - (BOOL)isRich;
    - (BOOL)isHandsome;
    @end
    
    
    MHYPerson.m 通过位运算实现三个属性的set、get方法
    #import "MHYPerson.h"
    
    // 掩码,一般用来按位与(&)运算的
    //#define MHYTallMask 1
    //#define MHYRichMask 2
    //#define MHYHandsomeMask 4
    
    //#define MHYTallMask 0b00000001
    //#define MHYRichMask 0b00000010
    //#define MHYHandsomeMask 0b00000100
    
    
    #define MHYTallMask (1<<0)
    #define MHYRichMask (1<<1)
    #define MHYHandsomeMask (1<<2)
    @interface MHYPerson()
    {
        char _tallRichHansome;//定义一个char类型,Bool类型的值是0和1,只需一个二进制位就可以存储,char类型占1个字节,1个字节8位;(一个字节足以存储3个属性的值)
    }
    @end
    
    @implementation MHYPerson
    - (instancetype)init
    {
        if (self = [super init]) {
            _tallRichHansome = 0b00000000;
        }
        return self;
    }
    
    - (void)setTall:(BOOL)tall
    {
        if (tall) {
            _tallRichHansome |= MHYTallMask;
        } else {
            _tallRichHansome &= ~MHYTallMask;
        }
    }
    
    - (BOOL)isTall
    {
        return !!(_tallRichHansome & MHYTallMask);
    }
    
    - (void)setRich:(BOOL)rich
    {
        if (rich) {
            _tallRichHansome |= MHYRichMask;
        } else {
            _tallRichHansome &= ~MHYRichMask;
        }
    }
    
    - (BOOL)isRich
    {
        return !!(_tallRichHansome & MHYRichMask);
    }
    
    - (void)setHandsome:(BOOL)handsome
    {
        if (handsome) {
            _tallRichHansome |= MHYHandsomeMask;
        } else {
            _tallRichHansome &= ~MHYHandsomeMask;
        }
    }
    
    - (BOOL)isHandsome
    {
        return !!(_tallRichHansome & MHYHandsomeMask);
    }
    @end
    
    

    main.m 类:

    #import <Foundation/Foundation.h>
    #import "MHYPerson.h"
    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            MHYPerson *person = [[MHYPerson alloc] init];
            person.tall = YES;
            person.rich = YES;
            person.handsome = YES;
            NSLog(@"tall:%d rich:%d hansome:%d", person.isTall, person.isRich, person.isHandsome);
        }
        return 0;
    }
    

    打印结果:

    2020-01-08 16:09:21.072299+0800 位运算添加属性[59170:365382] tall:1 rich:1 hansome:1
    Program ended with exit code: 0
    
    • 通过位运算选择多个枚举值
      比如:
    NSKeyValueObservingOptions options = NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld;
    

    例子:设定特殊的枚举值,2的倍数,通过&可计算得出。

    typedef enum {
        MHYOptionsNone = 0,     // 0b0000
        MHYOptionsOne = 1<<0,   // 0b0001
        MHYOptionsTwo = 1<<1,   // 0b0010
        MHYOptionsThree = 1<<2, // 0b0100
        MHYOptionsFour = 1<<3   // 0b1000
    } MHYOptions;
    @interface ViewController ()
    
    
    @implementation ViewController
    
    - (void)setOptions:(MHYOptions)options
    {
        if (!(options & MHYOptionsNone)) {
            NSLog(@"包含了MHYOptionsNone");
        }
        
        if (options & MHYOptionsOne) {
            NSLog(@"包含了MHYOptionsOne");
        }
        
        if (options & MHYOptionsTwo) {
            NSLog(@"包含了MHYOptionsTwo");
        }
        
        if (options & MHYOptionsThree) {
            NSLog(@"包含了MHYOptionsThree");
        }
    }
    
    - (void)viewDidLoad {
        [super viewDidLoad];
        [self setOptions:MHYOptionsOne|MHYOptionsFour];
    }
    
    @end
    

    打印结果:

    2020-01-12 18:21:22.236731+0800 Interview01-位运算[55728:300211] 包含了MHYOptionsNone
    2020-01-12 18:21:22.236847+0800 Interview01-位运算[55728:300211] 包含了MHYOptionsOne
    

    现在主菜来了,从这个例子入手,通过位域如何给类添加属性呢?

    • 位域:直接上代码(通过位域添加属性)
      MHYPerson类:
    #import <Foundation/Foundation.h>
    
    @interface MHYPerson : NSObject
    
    - (void)setTall:(BOOL)tall;
    - (void)setRich:(BOOL)rich;
    - (void)setHandsome:(BOOL)handsome;
    
    - (BOOL)isTall;
    - (BOOL)isRich;
    - (BOOL)isHandsome;
    @end
    
    #import "MHYPerson.h"
    @interface MHYPerson()
    {
        // 位域
        struct {
            char tall : 1;
            char rich : 1;
            char handsome : 1;
        } _tallRichHandsome;
    }
    @end
    
    @implementation MHYPerson
    - (void)setTall:(BOOL)tall
    {
        _tallRichHandsome.tall = tall;
    }
    
    - (BOOL)isTall
    {
        return !!_tallRichHandsome.tall;
    }
    
    - (void)setRich:(BOOL)rich
    {
        _tallRichHandsome.rich = rich;
    }
    
    - (BOOL)isRich
    {
        return !!_tallRichHandsome.rich;//不怎么做的话,赋值1有可能取值的是-1;因为bool返回的是8位,但是现在rich只有1位(ob1),强制转换8位,就会是0b11111111;但是如果在位域中为每个属性2位,就不会有这种情况。
    }
    
    - (void)setHandsome:(BOOL)handsome
    {
        _tallRichHandsome.handsome = handsome;
    }
    
    - (BOOL)isHandsome
    {
        return !!_tallRichHandsome.handsome;
    }
    
    @end
    
    

    main.m 类:

    #import <Foundation/Foundation.h>
    #import "MHYPerson.h"
    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            MHYPerson *person = [[MHYPerson alloc] init];
            person.tall = NO;
            person.rich = YES;
            person.handsome = YES;
            NSLog(@"tall:%d rich:%d hansome:%d", person.isTall, person.isRich, person.isHandsome);
        }
        return 0;
    }
    
    

    打印结果:

    2020-01-08 17:25:55.178796+0800 通过位域添加属性[73471:451017] tall:0 rich:1 hansome:1
    Program ended with exit code: 0
    
    (2)、共用体 union
    • 何为共用体
    • union中可以定义多个成员,union的大小由最大的成员的大小决定。
    • union成员共享同一块大小的内存,一次只能使用其中的一个成员。
    • 对某一个成员赋值,会覆盖其他成员的值(也不奇怪,因为他们共享一块内存。但前提是成员所占字节数相同,当成员所占字节数不同时只会覆盖相应字节上的值,比如对char成员赋值就不会把整个int成员覆盖掉,因为char只占一个字节,而int占四个字节)
    • union的存放顺序是所有成员都从低地址开始存放的

    使用共用体可以使代码存储数据高效率的同时,有较强的可读性,可以使用共用体来增强代码可读性,同时使用位运算来提高数据存取的效率。

    • 上代码解析共用体
      MHYPerson类:
    #import <Foundation/Foundation.h>
    
    @interface MHYPerson : NSObject
    
    - (void)setTall:(BOOL)tall;
    - (void)setRich:(BOOL)rich;
    - (void)setHandsome:(BOOL)handsome;
    - (void)setThin:(BOOL)thin;
    
    - (BOOL)isTall;
    - (BOOL)isRich;
    - (BOOL)isHandsome;
    - (BOOL)isThin;
    @end
    
    #import "MHYPerson.h"
    
    #define MHYTallMask (1<<0)
    #define MHYRichMask (1<<1)
    #define MHYHandsomeMask (1<<2)
    #define MHYThinMask (1<<3)
    
    /*利用位域增加可读性(纯粹摆设),利用位运算进行数据存储*/
    /*class、mata-class的地址值,最后三位一定是0*/
    @interface MHYPerson()
    {
         union {
             char bits; //占一个字节;用来存取数据信息;
             struct {
                 char tall : 1;
                 char rich : 1;
                 char handsome : 1;
                 char thin : 1;
             };
         } _tallRichHandsome;
    }
    @end
    
    @implementation MHYPerson
    
    - (void)setTall:(BOOL)tall
    {
        if (tall) {
            _tallRichHandsome.bits |= MHYTallMask;
        } else {
            _tallRichHandsome.bits &= ~MHYTallMask;
        }
    }
    
    - (BOOL)isTall
    {
        return !!(_tallRichHandsome.bits & MHYTallMask);
    }
    
    - (void)setRich:(BOOL)rich
    {
        if (rich) {
            _tallRichHandsome.bits |= MHYRichMask;
        } else {
            _tallRichHandsome.bits &= ~MHYRichMask;
        }
    }
    
    - (BOOL)isRich
    {
        return !!(_tallRichHandsome.bits & MHYRichMask);
    }
    
    - (void)setHandsome:(BOOL)handsome
    {
        if (handsome) {
            _tallRichHandsome.bits |= MHYHandsomeMask;
        } else {
            _tallRichHandsome.bits &= ~MHYHandsomeMask;
        }
    }
    
    - (BOOL)isHandsome
    {
        return !!(_tallRichHandsome.bits & MHYHandsomeMask);
    }
    
    
    
    - (void)setThin:(BOOL)thin
    {
        if (thin) {
            _tallRichHandsome.bits |= MHYThinMask;
        } else {
            _tallRichHandsome.bits &= ~MHYThinMask;
        }
    }
    
    - (BOOL)isThin
    {
        return !!(_tallRichHandsome.bits & MHYThinMask);
    }
    
    @end
    

    main.m类:

    #import <Foundation/Foundation.h>
    #import "MHYPerson.h"
    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            MHYPerson *person = [[MHYPerson alloc] init];
            person.tall = NO;
            person.rich = YES;
            person.handsome = NO;
            person.thin = YES;
            NSLog(@"tall:%d rich:%d hansome:%d thin:%d ",person.isTall, person.isThin, person.isRich, person.isHandsome);
        }
        return 0;
    }
    

    打印结果:

    2020-01-09 16:38:49.211422+0800 通过共用体添加属性[13128:1190952] tall:0 rich:1 hansome:1 thin:0
    Program ended with exit code: 0
    

    通过上述三个例子,我们对位运算、位域、共用体有了一定的了解了,那么我们就回归主题,继续探讨isa指针。


    isa指针

    从上图我们可以看出,用bits来存储isa指针所有的数据。

    下面我们来解析一下位域中的都代表什么: isa指针-位域信息

    ISA_MASK 0x0000000ffffffff8ULL

    换成二进制:

    0b0000 0000 0000 0000 0000 0000 0000 1111 1111 1111 1111 1111 1111 1111 1111 1000

    问题:为什么获取类/元类对象的地址值,为什么要用isa的地址&ISA_MASK((Class)(isa.bits & ISA_MASK)),才能获取类/元类对象的地址。
    因为:arm64架构之后,isa指针被优化成了共用体,然后共用体里面的位域(shiftcls占了64位中间的33位)。要想获取shiftcls代表的地址,只能& ISA_MASK(33位1)。

    问题:class、mata-class的地址值,最后三位一定是0。
    因为:低位的不能消除,必须保留着。
    例子:必须创建iOS工程,真机运行。否则不准确,因为mac和模拟器用的是x86_64架构的

        NSLog(@"%p", [ViewController class]);
        NSLog(@"%p", object_getClass([ViewController class]));
        NSLog(@"%p", [MHYPerson class]);
        NSLog(@"%p", object_getClass([MHYPerson class]));
    

    打印结果:

    2020-01-09 19:04:12.266 Interview04-Class地址[3291:1331928] 0x100028dc0
    2020-01-09 19:04:12.267 Interview04-Class地址[3291:1331928] 0x100028de8
    2020-01-09 19:04:12.267 Interview04-Class地址[3291:1331928] 0x100028e88
    2020-01-09 19:04:12.267 Interview04-Class地址[3291:1331928] 0x100028e60
    

    一个十六进制位代表4个二进制位。
    0x100028dc0 最后一位准成二进制位:0000
    0x100028de8最后一位准成二进制位: 1000
    可以得出class、mata-class的地址值,最后三位一定是0。

    (二)、Class的结构

    类的所有方法,属性、成员变量、协议一开始都是存储在class_ro_t里,当我们程序运行起来时,才将分类的信息和我们之前类的信息合并到一起赋值到class_rw_t里面。所以class_rw_t的信息,有一部分是class_ro_t的。所以一开始不存在class_rw_t。

    我们知道实例对象想调用对象方法:

    • 通过isa指针找到类对象,去类对象的cache里面看一下有没有方法缓存,由于第一次调用的时候,cache里面是空的(就是没有这个方法),于是就通过bits去class_rw_t里面去找(遍历methods数组去找),如果找到有这个方法,就调用这个方法,并且把这个方法存到自己的cache缓存里面;下次去调用这个方法的时候,直接通过isa指针得到类对象,然后在类对象的cache里面去取,这次找到就直接调用,就不会再通过bits去class_rw_t遍历methods了。
    • 如果对象方法不是在当前类里面(假设在父类/基类里面),通过isa指针找到类对象,去类对象的cache里面看一下有没有方法缓存,但是一开cache里面是空的,就通过bits去class_rw_t里面去找(遍历methods数组去找),还是没找到就通过superclass指针找到父类的类对象,先看父类的cache里面是否有这个方法,如果有就拿过来调用,并且缓存到自己类对象里面(下次就直接取自己类对象里面的对象方法就可以了,就不用再去父类里面找了); 如果没有找到,那就通过父类的bits去class_rw_t里面去找(遍历methods数组去找),如果找到,就调用这个方法,并且缓存到自己的类对象里面,下次调用直接去自己的cache里面去去就行。若果父类的类对象里面还是没有,那就再通过superclass一层一层往上找,直到找到为止。


      Class的结构
    (1)、class_rw_t(读写)

    class_rw_t里面的methods、properties、protocols是二维数组,是可读可写的,包含了类的初始内容、分类的内容


    class_rw_t
    (2)、class_ro_t(只读)

    class_ro_t里面的baseMethodList、baseProtocols、ivars、baseProperties是一维数组,是只读的,包含了类的初始内容。


    class_ro_t
    (2)、method_t
    • method_t是对方法\函数的封装(结构体)


      method_t
    • IMP代表函数的具体实现 image.png
    • SEL代表方法\函数名,一般叫做选择器,底层结构跟char *类似

    • 可以通过@selector()和sel_registerName()获得
    • 可以通过sel_getName()和NSStringFromSelector()转成字符串
    • 不同类中相同名字的方法,所对应的方法选择器是相同的
    SEL
    • types包含了函数的返回值、参数编码的字符串。


      image.png
    image.jpeg
    (3)、Type Encoding
    iOS中提供了一个叫做@encode的指令,可以将具体的类型表示成字符串编码。 Type Encoding
    (4)、cache_t(方法缓存)
    Class内部结构中有个方法缓存(cache_t),用散列表(哈希表)来缓存曾经调用过的方法,可以提高方法的查找速度。(散列表底层就是个数组) cache_t

    objc-cache.mm (源码文件)
    bucket_t * cache_t::find(cache_key_t k, id receiver

    • 缓存查找

    怎么查找呢? 通过key&mask查找(索引)


    说明
    • 直接上代码:
      HYClassInfo.h c++文件
    //
    //  HYClassInfo.h
    //  方法缓存-cache
    //
    //  Created by 莫浩洋 on 2020/1/13.
    //  Copyright © 2020 mhy. All rights reserved.
    //
    
    #ifndef HYClassInfo_h
    #define HYClassInfo_h
    
    # if __arm64__
    #   define ISA_MASK        0x0000000ffffffff8ULL
    # elif __x86_64__
    #   define ISA_MASK        0x00007ffffffffff8ULL
    # endif
    
    #if __LP64__
    typedef uint32_t mask_t;
    #else
    typedef uint16_t mask_t;
    #endif
    typedef uintptr_t cache_key_t;
    
    #if __arm__  ||  __x86_64__  ||  __i386__
    // objc_msgSend has few registers available.
    // Cache scan increments and wraps at special end-marking bucket.
    #define CACHE_END_MARKER 1
    static inline mask_t cache_next(mask_t i, mask_t mask) {
        return (i+1) & mask;
    }
    
    #elif __arm64__
    // objc_msgSend has lots of registers available.
    // Cache scan decrements. No end marker needed.
    #define CACHE_END_MARKER 0
    static inline mask_t cache_next(*m***ask_t i, mask_t mask) {
        return i ? i-1 : mask;
    }
    
    #else
    #error unknown architecture
    #endif
    
    struct bucket_t {
        cache_key_t _key;
        IMP _imp;
    };
    
    struct cache_t {
        bucket_t *_buckets;
        mask_t _mask;
        mask_t _occupied;
        
        IMP imp(SEL selector)
        {
            mask_t begin = _mask & (long long)selector;
            mask_t i = begin;
            do {
                if (_buckets[i]._key == 0  ||  _buckets[i]._key == (long long)selector) {
                    return _buckets[i]._imp;
                }
            } while ((i = cache_next(i, _mask)) != begin);
            return NULL;
        }
    };
    
    struct entsize_list_tt {
        uint32_t entsizeAndFlags;
        uint32_t count;
    };
    
    struct method_t {
        SEL name;
        const char *types;
        IMP imp;
    };
    
    struct method_list_t : entsize_list_tt {
        method_t first;
    };
    
    struct ivar_t {
        int32_t *offset;
        const char *name;
        const char *type;
        uint32_t alignment_raw;
        uint32_t size;
    };
    
    struct ivar_list_t : entsize_list_tt {
        ivar_t first;
    };
    
    struct property_t {
        const char *name;
        const char *attributes;
    };
    
    struct property_list_t : entsize_list_tt {
        property_t first;
    };
    
    struct chained_property_list {
        chained_property_list *next;
        uint32_t count;
        property_t list[0];
    };
    
    typedef uintptr_t protocol_ref_t;
    struct protocol_list_t {
        uintptr_t count;
        protocol_ref_t list[0];
    };
    
    struct class_ro_t {
        uint32_t flags;
        uint32_t instanceStart;
        uint32_t instanceSize;  // instance对象占用的内存空间
    #ifdef __LP64__
        uint32_t reserved;
    #endif
        const uint8_t * ivarLayout;
        const char * name;  // 类名
        method_list_t * baseMethodList;
        protocol_list_t * baseProtocols;
        const ivar_list_t * ivars;  // 成员变量列表
        const uint8_t * weakIvarLayout;
        property_list_t *baseProperties;
    };
    
    struct class_rw_t {
        uint32_t flags;
        uint32_t version;
        const class_ro_t *ro;
        method_list_t * methods;    // 方法列表
        property_list_t *properties;    // 属性列表
        const protocol_list_t * protocols;  // 协议列表
        Class firstSubclass;
        Class nextSiblingClass;
        char *demangledName;
    };
    
    #define FAST_DATA_MASK          0x00007ffffffffff8UL
    struct class_data_bits_t {
        uintptr_t bits;
    public:
        class_rw_t* data() {
            return (class_rw_t *)(bits & FAST_DATA_MASK);
        }
    };
    
    /* OC对象 */
    struct hy_objc_object {
        void *isa;
    };
    
    /* 类对象 */
    struct hy_objc_class : hy_objc_object {
        Class superclass;
        cache_t cache;
        class_data_bits_t bits;
    public:
        class_rw_t* data() {
            return bits.data();
        }
        
        hy_objc_class* metaClass() {
            return (hy_objc_class *)((long long)isa & ISA_MASK);
        }
    };
    
    #endif /* HYClassInfo_h */
    

    HYPerson 、HYStudent、HYGoodStudent三个类(HYGoodStudent继承自HYStudent,HYStudent继承自HYPerson)

    HYPerson

    HYPerson.h
    
    #import <Foundation/Foundation.h>
    
    @interface MJPerson : NSObject
    - (void)personTest;
    @end
    
    
    HYPerson.m
    #import "HYPerson.h"
    
    @implementation HYPerson
    - (void)personTest
    {
        NSLog(@"%s", __func__);
    }
    @end
    
    

    HYStudent

    HYStudent.h
    #import "HYPerson.h"
    
    NS_ASSUME_NONNULL_BEGIN
    
    @interface HYStudent : HYPerson
    - (void)studentTest;
    @end
    
    NS_ASSUME_NONNULL_END
    
    
    HYStudent.m
    #import "HYStudent.h"
    
    @implementation HYStudent
    - (void)studentTest
    {
        NSLog(@"%s", __func__);
    }
    @end
    
    

    HYGoodStudent

    HYGoodStudent.h
    
    #import "HYStudent.h"
    
    NS_ASSUME_NONNULL_BEGIN
    
    @interface HYGoodStudent : HYStudent
    - (void)goodStudentTest;
    @end
    
    NS_ASSUME_NONNULL_END
    
    
    HYGoodStudent.m
    #import "HYGoodStudent.h"
    
    @implementation HYGoodStudent
    - (void)goodStudentTest
    {
        NSLog(@"%s", __func__);
    }
    @end
    

    main.mm:因为引入了c++文件,所以后缀必须为.mm

    #import <Foundation/Foundation.h>
    #import "HYGoodStudent.h"
    #import "HYClassInfo.h"
    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            HYGoodStudent *goodStudent = [[HYGoodStudent alloc] init];
            hy_objc_class *goodStudentClass = (__bridge hy_objc_class *)[HYGoodStudent class];
            
            [goodStudent goodStudentTest];
            [goodStudent studentTest];
            [goodStudent personTest];
            [goodStudent goodStudentTest];
            [goodStudent studentTest];
            
            
            NSLog(@"----------------直接取缓存的方法-----------------------------------");
            
            cache_t cache = goodStudentClass->cache;
            bucket_t *buckets = cache._buckets;
            bucket_t bucket = buckets[(long long)@selector(goodStudentTest) & cache._mask];
            NSLog(@"%s %p", bucket._key, bucket._imp);
            
            
            NSLog(@"------------------这样取出来一定是对的-----------------------");
            NSLog(@"%s %p", @selector(personTest), cache.imp(@selector(personTest)));
            NSLog(@"%s %p", @selector(studentTest), cache.imp(@selector(studentTest)));
            NSLog(@"%s %p", @selector(goodStudentTest), cache.imp(@selector(goodStudentTest)));
        
            
             NSLog(@"----------------------遍历散列表--------------------------");
            for (int i = 0; i<=cache._mask; i++) {
                bucket_t bucket = buckets[I];
                NSLog(@"%s %p", bucket._key, bucket._imp);
            }
        }
        return 0;
    }
    
    
    • _mask的长度、缓存


      personTest未缓存
      personTest已经缓存
    • 缓存列表的长度不够,会瞬间扩容,并把之前缓存清空(旧容量*2)


      未扩容-init缓存
      未扩容-init、goodStudentTest、studentTest已缓存
      扩容后
    • 获取散列表的所有缓存(bucket)


      未执行对象方法
      调用了goodStudentTest方法
    临界点 扩容后 扩容后
    • 直接从散列表取出对应的缓存方法,不通过遍历


      &mask
    不对应和一定对应

    对于init的方法缓存:如果有自己的init方法,调用哪个就缓存自己的init方法,没有则缓存父类的。

    相关文章

      网友评论

        本文标题:Runtime(一)

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