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