一、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_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 *类似
SEL
- 可以通过@selector()和sel_registerName()获得
- 可以通过sel_getName()和NSStringFromSelector()转成字符串
- 不同类中相同名字的方法,所对应的方法选择器是相同的
-
types包含了函数的返回值、参数编码的字符串。
image.png
(3)、Type Encoding
iOS中提供了一个叫做@encode的指令,可以将具体的类型表示成字符串编码。 Type Encoding(4)、cache_t(方法缓存)
Class内部结构中有个方法缓存(cache_t),用散列表(哈希表)来缓存曾经调用过的方法,可以提高方法的查找速度。(散列表底层就是个数组) cache_tobjc-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方法,没有则缓存父类的。
网友评论