一,OC对象本质,用clang编译main.m
OC对象结构都是通过基础的C/C++结构体实现的,我们通过创建OC文件及对象,将OC对象转化为C++文件来探寻OC对象的本质。
代码:
@interface HTPerson : NSObject
@property(nonatomic,strong)NSString *name;
@end
@implementation HTPerson
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
HTPerson *person = [[HTPerson alloc]init];
NSLog(@"Hello, World!");
}
return 0;
}
通过命令行将OC的main文件转化为C++文件
我们通过命令行将OC的mian.m文件转化为c++文件。
复制代码clang -rewrite-objc main.m -o main.cpp // 这种方式没有指定架构例如arm64架构 其中cpp代表(c plus plus)
生成 main.cpp
我们可以指定架构模式的命令行,使用xcode工具 xcrun
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m -o main-arm64.cpp
生成 main-arm64.cpp
#ifndef _REWRITER_typedef_HTPerson
#define _REWRITER_typedef_HTPerson
typedef struct objc_object HTPerson;
typedef struct {} _objc_exc_HTPerson;
#endif
extern "C" unsigned long OBJC_IVAR_$_HTPerson$_name;
struct HTPerson_IMPL {
struct NSObject_IMPL NSObject_IVARS;
NSString *_name;
};
// @property (nonatomic, strong) NSString* name;
/* @end */
// @implementation HTPerson
static NSString * _I_HTPerson_name(HTPerson * self, SEL _cmd) { return (*(NSString **)((char *)self + OBJC_IVAR_$_HTPerson$_name)); }
static void _I_HTPerson_setName_(HTPerson * self, SEL _cmd, NSString *name) { (*(NSString **)((char *)self + OBJC_IVAR_$_HTPerson$_name)) = name; }
// @end
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
HTPerson* person = ((HTPerson *(*)(id, SEL))(void *)objc_msgSend)((id)((HTPerson *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("HTPerson"), sel_registerName("alloc")), sel_registerName("init"));
}
return 0;
}
可以观察到HTPerson
类的对象的本质就是结构体(struct)
struct HTPerson_IMPL {
struct NSObject_IMPL NSObject_IVARS;
NSString *_name;
};
struct NSObject_IMPL NSObject_IVARS
是继承于父类NSObject的实例变量。这种方法模拟了类继承的行为,确保HTPerson对象不仅拥有自己的实例变量_name,还继承了NSOBject的所有实例变量。
typedef struct objc_class *Class;
struct NSObject_IMPL {
Class isa;
};
我们发现class
其实就是一个指针,isa是一个指向类对象的指针,每个OC对象都有一个isa指针,指向它的类。
extern "C" unsigned long OBJC_IVAR_$_HTPerson$_name;
这声明了一个全局变量用于_name
实例变量在HTPerson
对象中的偏移量。编译器使用这个变量来访问对象的实例变量。
下面是HTPerson属性的set,get
方法的底层实现
static NSString * _I_HTPerson_name(HTPerson * self, SEL _cmd) { return (*(NSString **)((char *)self + OBJC_IVAR_$_HTPerson$_name)); }
static void _I_HTPerson_setName_(HTPerson * self, SEL _cmd, NSString *name) { (*(NSString **)((char *)self + OBJC_IVAR_$_HTPerson$_name)) = name; }
在set、get方法的底层实现中 OBJC_IVAR_$_SMPerson$_name
就是属性对应的内存地址的偏移量, 系统通过每个属性的偏移量,来实现对其赋值和取值。
typedef struct objc_object HTPerson;
将objc_object
结构体类型定义为HTPerson
类型
- objc_object:包含一个指向类的指针isa的结构体,所有OC对象都继承自它。
typedef struct objc_class *Class;
struct objc_object {
Class _Nonnull isa __attribute__((deprecated));
};
- HTPerson:这行代码定义了一个新的类型名
HTPerson
,它是struct objc_object
类型的别名。
二,OC对象在内存中的布局
以NSObject为例,点击NSObject进入发现NSObject的内部实现
@interface NSObject <NSObject> {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wobjc-interface-ivars"
Class isa OBJC_ISA_AVAILABILITY;
#pragma clang diagnostic pop
}
@end
转化为c语言函数就是一个结构体
struct NSObject_IMPL {
Class isa;
};
这个结构体只有一个成员isa指针,而指针在64位架构中占8个字节,也就是说一个NSObject
对象所暂用的内存是8
个字节。对于OC对象还有一些方法,这些方法也是占用内存的,但这些方法所占用的空间并不在NSObject对象中。
探寻NSObject对象在内存中如何体现的
NSObject *objc = [[NSObject alloc] init];
上述一段代码中系统为NSObject对象分配8个字节的内存空间,用来存放一个成员isa指针。那么isa指针这个变量的地址就是结构体的起始地址,也就是NSObjcet对象的地址。
假设isa的地址为0x100400110,那么上述代码分配存储空间给NSObject对象,然后将存储空间的地址赋值给objc指针。objc存储的就是isa的地址。objc指向内存中NSObject对象地址,即指向内存中的结构体,也就是isa的位置。
自定义类的内部实现
#import <Foundation/Foundation.h>
#import <objc/runtime.h>
@interface HTPerson : NSObject
@property (nonatomic, strong) NSString* name;
@property (nonatomic) int number;
@property (nonatomic, strong) NSString* phone;
@end
@implementation HTPerson
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
HTPerson* person = [[HTPerson alloc] init];
person.name = @"name";
person.number = 1;
person.phone = @"123";
NSLog(@"%zd", class_getInstanceSize([HTPerson class]));
}
return 0;
}
struct HTPerson_IMPL {
struct NSObject_IMPL NSObject_IVARS;
int _number;
NSString *_name;
NSString *_phone;
};
NSObject_IMPL内部其实就是Class isa 那么我们假设 struct NSObject_IMPL NSObject_IVARS; 等价于 Class isa;
所以可以转化为
struct HTPerson_IMPL {
Class *isa;
int _number;
NSString *_name;
NSString *_phone;
};
因此此结构体占用多少存储空间,对象就占用多少存储空间。输出结果为32.
现在我们来计算总的内存大小,考虑内存对齐:
- NSObject_IVARS(假设它包含一个指针):8字节
- _number:4字节
对齐填充:4字节(因为接下来是指针,按8字节对齐) - _name:8字节
- _phone:8字节
所以OC对象的内存布局也遵守结构体的内存对齐规则。
- 前面的地址必须是后面的地址正数倍,不是就补齐。
- 整个Struct的地址必须是最大字节的整数倍。
复杂的继承关系
/* Person */
@interface Person : NSObject
{
int _age;
}
@end
@implementation Person
@end
/* Student */
@interface Student : Person
{
int _no;
}
@end
@implementation Student
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
NSLog(@"%zd %zd",
class_getInstanceSize([Person class]),
class_getInstanceSize([Student class])
);
}
return 0;
}
画出内存图例
我们发现只要继承自NSObject
的对象,那么底层结构体内一定有一个isa
指针。
上面的输出结果为16,16。
person对象只使用了12个字节,但是因为内存对齐的原因,使person对象也占用16个字节。对于student对象,包含了person对象的结构体实现,和一个int类型的_no成员变量,同样isa指针8个字节,_age成员变量4个字节,_no成员变量4个字节,根据内存对齐原则student对象占据的内存空间也是16个字节。
类的本质
OC的类信息存放在哪里? OC对象主要可以分为三种
- instance对象(实例对象)
- class对象(类对象)
- meta-class对象(元类对象)
instance对象就是通过类alloc出来的对象,每次调用alloc都会产生新的instance对象。
NSObjcet *object1 = [[NSObjcet alloc] init];
NSObjcet *object2 = [[NSObjcet alloc] init];
object1
和object2
都是NSObject
的instace
对象(实例对象),但他们是不同的两个对象,并且分别占据着两块不同的内存。 instance对象在内存中存储的信息包括isa指针和其他成员变量
class对象 我们通过class方法或runtime方法得到一个class对象。class对象也就是类对象
Class objectClass1 = [object1 class];//获取类对象
Class objectClass2 = [NSObject class];//获取元类
// runtime
Class objectClass4 = object_getClass(object1);
每一个类在内存中有且只有一个对象。
class对象在内存中存储的信息主要包括
- isa指针
- superclass指针
- 类的属性信息(@property),类的成员变量信息(ivar)
- 类的对象方法信息(instance method),类的协议信息(protocol)
instance对象中的成员变量是具体对象的属性,每个实例都有自己的一份副本,是成员变量具体的值。创建实例对象的时候要为成员变量赋值。而类对象中的类的成员变量信息与类本身关联,而不是与类的每个实例关联。
meta_class对象 每个类在内存中有且只有一个meta-class对象。在内存中存储的信息主要包括
- isa指针
- superclass指针
- 类的类方法的信息(class method)
meta-class对象
和class对象
的内存结构是一样的,所以meta-class中也有类的属性信息,类的对象方法信息等成员变量,但是其中的值可能是空的。
对象的isa指针的指向
- 当对象调用实例方法的时候,实例方法信息是存储在class类对象中的,instance的isa指向class,通过instance的isa找到
class
,最后找到对象方法的实现进行调用。 - 当类对象调用类方法的时候,类方法是存储在meta-class元类对象中的。class类对象的isa指针就指向元类对象,通过class的isa找到
meta-class
,最后找到类方法的实现进行调用
- 当对象调用其父类对象方法的时候,此时就需要使用到class类对象superclass指针。当Student的instance对象要调用Person的对象方法时,会先通过
isa
找到Student的class
,然后通过superclass
找到Person的class
,最后找到对象方法的实现进行调用。 - 当类对象调用父类的类方法时,当Student的class要调用Person的类方法时,会先通过isa找到Student的meta-class,然后通过
superclass
找到Person的meta-class
,最后找到类方法的实现进行调用
不管是类对象还是元类对象,类型都是Class
,class
和mete-class
的底层都是objc_class结构体的指针,内存中就是结构体
struct objc_class : objc_object {
...省略无关代码
// Class ISA; //ISA(从objc_object继承过来的)指向元类
Class superclass; //指向其父类
cache_t cache; //缓存
class_data_bits_t bits; //类的数据
class_rw_t *data() const {
return bits.data();
}
void setData(class_rw_t *newData) {
bits.setData(newData);
}
...省略无关代码
}
这个结构体继承 objc_object
并且结构体内有一些函数,因为这是c++结构体,在c上做了扩展,因此结构体中可以包含函数。
前面讲到bjc_object中有一个isa指针,那么objc_class
继承objc_objec
t,也就同样拥有一个isa
指针。
类中存储的类的成员变量信息,实例方法,属性名等这些信息在哪里呢。
class_data_bits_t bits; //类的数据
class_rw_t *data() const {
return bits.data();
}
void setData(class_rw_t *newData) {
bits.setData(newData);
}
obj_class中有class_rw_t
类型的bits.data
的读写
isa_t
对象的isa指针并不是直接指向类对象或者元类对象,需要&ISA_MASK位运算才获得类对象或者元类对象的内存地址
isa指针其实是一个isa_t的共用体
// 精简过的isa_t共用体
union isa_t
{
isa_t() { }
isa_t(uintptr_t value) : bits(value) { }
Class cls;
uintptr_t bits;
#if SUPPORT_PACKED_ISA
# if __arm64__
# define ISA_MASK 0x0000000ffffffff8ULL
# define ISA_MAGIC_MASK 0x000003f000000001ULL
# define ISA_MAGIC_VALUE 0x000001a000000001ULL
struct {
uintptr_t nonpointer : 1;
uintptr_t has_assoc : 1;
uintptr_t has_cxx_dtor : 1;
uintptr_t shiftcls : 33; // MACH_VM_MAX_ADDRESS 0x1000000000
uintptr_t magic : 6;
uintptr_t weakly_referenced : 1;
uintptr_t deallocating : 1;
uintptr_t has_sidetable_rc : 1;
uintptr_t extra_rc : 19;
# define RC_ONE (1ULL<<45)
# define RC_HALF (1ULL<<18)
};
# elif __x86_64__
# define ISA_MASK 0x00007ffffffffff8ULL
# define ISA_MAGIC_MASK 0x001f800000000001ULL
# define ISA_MAGIC_VALUE 0x001d800000000001ULL
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;
# define RC_ONE (1ULL<<56)
# define RC_HALF (1ULL<<7)
};
# else
# error unknown architecture for packed isa
# endif
#endif
isa_t是union类型,表示共用体。可以看到共用体中有一个结构体,结构体内部分别定义了一些变量,变量后面的值代表的是该变量占用多少个字节,也就是位域技术
位域:把一个字节中的二进位划分为几个不同的区域,并说明每个区域的位数。每个域有一个域名,允许在程序中按域名进行操作。这样就可以把几个不同的对象用一个字节的二进制位域来表示。
共用体:在进行某些算法的C语言编程的时候,需要使几种不同类型的变量存放到同一段内存单元中。也就是使用覆盖技术,几个变量互相覆盖。这种几个不同的变量共同占用一段内存的结构,在C语言中,被称作“共用体”类型结构,简称共用体。
结构体仅仅是为了增加代码的可读性,指明共用体中存储了那些成员信息以及占用的空间。
源码中通过共用体的形式存储了64位的值,这些值在结构体中展示出来,通过对bits同ISA_MASK进行位运算取出相应位置的值,这样就解释了对象的isa指针,需要&ISA_MASK位运算才获得类对象或者元类对象的内存地址
isa中存储的信息及作用
struct {
// 0代表普通的指针,存储着Class,Meta-Class对象的内存地址。
// 1代表优化后的使用位域存储更多的信息。
uintptr_t nonpointer : 1;
// 是否有设置过关联对象,如果没有,释放时会更快
uintptr_t has_assoc : 1;
// 是否有C++析构函数,如果没有,释放时会更快
uintptr_t has_cxx_dtor : 1;
// 存储着Class、Meta-Class对象的内存地址信息
uintptr_t shiftcls : 33;
// 用于在调试时分辨对象是否未完成初始化
uintptr_t magic : 6;
// 是否有被弱引用指向过。
uintptr_t weakly_referenced : 1;
// 对象是否正在释放
uintptr_t deallocating : 1;
// 引用计数器是否过大无法存储在isa中
// 如果为1,那么引用计数会存储在一个叫SideTable的类的属性中
uintptr_t has_sidetable_rc : 1;
// 里面存储的值是引用计数器减1
uintptr_t extra_rc : 19;
};
ISA_MASK的值0x0000000ffffffff8ULL,我们将其转化为二进制数
ISA_MASK的值中有33位为1,通过与isa指针(8字节)进行按位与的操作就可以取出isa中这33位的值,刚好对应的shiftcls
中存储着Class、Meta-Class对象的内存地址信息。
class_rw_t
我们来到class_rw_t
中,class_rw_t 结构体是 Objective-C 运行时中用于描述类的可读写数据的结构体。它包含了类的动态数据,可以在运行时修改。这些数据包括方法列表、属性列表、协议列表等。
struct class_rw_t {
// Be warned that Symbolication knows the layout of this structure.
uint32_t flags;
uint16_t witness;
#if SUPPORT_INDEXED_ISA
uint16_t index;
#endif
explicit_atomic<uintptr_t> ro_or_rw_ext;
Class firstSubclass;
Class nextSiblingClass;
private:
using ro_or_rw_ext_t = objc::PointerUnion<const class_ro_t, class_rw_ext_t, PTRAUTH_STR("class_ro_t"), PTRAUTH_STR("class_rw_ext_t")>;
const ro_or_rw_ext_t get_ro_or_rwe() const {
return ro_or_rw_ext_t{ro_or_rw_ext};
}
void set_ro_or_rwe(const class_ro_t *ro) {
ro_or_rw_ext_t{ro, &ro_or_rw_ext}.storeAt(ro_or_rw_ext, memory_order_relaxed);
}
void set_ro_or_rwe(class_rw_ext_t *rwe, const class_ro_t *ro) {
// the release barrier is so that the class_rw_ext_t::ro initialization
// is visible to lockless readers
rwe->ro = ro;
ro_or_rw_ext_t{rwe, &ro_or_rw_ext}.storeAt(ro_or_rw_ext, memory_order_release);
}
class_rw_ext_t *extAlloc(const class_ro_t *ro, bool deep = false);
public:
void setFlags(uint32_t set)
{
__c11_atomic_fetch_or((_Atomic(uint32_t) *)&flags, set, __ATOMIC_RELAXED);
}
void clearFlags(uint32_t clear)
{
__c11_atomic_fetch_and((_Atomic(uint32_t) *)&flags, ~clear, __ATOMIC_RELAXED);
}
// set and clear must not overlap
void changeFlags(uint32_t set, uint32_t clear)
{
ASSERT((set & clear) == 0);
uint32_t oldf, newf;
do {
oldf = flags;
newf = (oldf | set) & ~clear;
} while (!CompareAndSwap(oldf, newf, &flags));
}
class_rw_ext_t *ext() const {
return get_ro_or_rwe().dyn_cast<class_rw_ext_t *>(&ro_or_rw_ext);
}
class_rw_ext_t *extAllocIfNeeded() {
auto v = get_ro_or_rwe();
if (fastpath(v.is<class_rw_ext_t *>())) {
return v.get<class_rw_ext_t *>(&ro_or_rw_ext);
} else {
return extAlloc(v.get<const class_ro_t *>(&ro_or_rw_ext));
}
}
class_rw_ext_t *deepCopy(const class_ro_t *ro) {
return extAlloc(ro, true);
}
const class_ro_t *ro() const {
auto v = get_ro_or_rwe();
if (slowpath(v.is<class_rw_ext_t *>())) {
return v.get<class_rw_ext_t *>(&ro_or_rw_ext)->ro;
}
return v.get<const class_ro_t *>(&ro_or_rw_ext);
}
void set_ro(const class_ro_t *ro) {
auto v = get_ro_or_rwe();
if (v.is<class_rw_ext_t *>()) {
v.get<class_rw_ext_t *>(&ro_or_rw_ext)->ro = ro;
} else {
set_ro_or_rwe(ro);
}
}
const method_array_t methods() const {
auto v = get_ro_or_rwe();
if (v.is<class_rw_ext_t *>()) {
return v.get<class_rw_ext_t *>(&ro_or_rw_ext)->methods;
} else {
return method_array_t{v.get<const class_ro_t *>(&ro_or_rw_ext)->baseMethods};
}
}//方法列表
const property_array_t properties() const {
auto v = get_ro_or_rwe();
if (v.is<class_rw_ext_t *>()) {
return v.get<class_rw_ext_t *>(&ro_or_rw_ext)->properties;
} else {
return property_array_t{v.get<const class_ro_t *>(&ro_or_rw_ext)->baseProperties};
}
}//属性列表
const protocol_array_t protocols() const {
auto v = get_ro_or_rwe();
if (v.is<class_rw_ext_t *>()) {
return v.get<class_rw_ext_t *>(&ro_or_rw_ext)->protocols;
} else {
return protocol_array_t{v.get<const class_ro_t *>(&ro_or_rw_ext)->baseProtocols};
}
}//协议列表
}
method_array_t、property_array_t、protocol_array_t都继承于模板类list_array_tt(OC支持动态更新数据的核心函数),它们内部实现了添加、存储、释放等管理函数。
method_array_t本身就是一个二维数组,数组里面存放的是数组method_list_t,method_list_t里面最终存放的是method_t
method_t
的结构体
struct method_t {
SEL name; // 函数名
const char *types; // 编码(返回值类型,参数类型)
IMP imp; // 指向函数的指针(函数地址)
};
class_ro_t
而成员变量信息则是存储在class_ro_t
内部中的,我们来到class_ro_t内查看。
class_ro_t 结构体是 Objective-C 运行时中用于描述类的只读
数据的重要部分,包含了类的方法列表、属性列表、协议列表成员变脸等信息。内部直接存储的直接就是method_list_t、protocol_list_t 、property_list_t,ivar_list_t *类型的一维数组,数组里面分别存放的是类的初始信息
struct class_ro_t {
uint32_t flags;
uint32_t instanceStart;
uint32_t instanceSize;//实例对象大小
#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;
// This field exists only when RO_HAS_SWIFT_INITIALIZER is set.
_objc_swiftMetadataInitializer __ptrauth_objc_method_list_imp _swiftMetadataInitializer_NEVER_USE[0];
_objc_swiftMetadataInitializer swiftMetadataInitializer() const {
if (flags & RO_HAS_SWIFT_INITIALIZER) {
return _swiftMetadataInitializer_NEVER_USE[0];
} else {
return nil;
}
}
method_list_t *baseMethods() const {
return baseMethodList;
}
class_ro_t *duplicate() const {
if (flags & RO_HAS_SWIFT_INITIALIZER) {
size_t size = sizeof(*this) + sizeof(_swiftMetadataInitializer_NEVER_USE[0]);
class_ro_t *ro = (class_ro_t *)memdup(this, size);
ro->_swiftMetadataInitializer_NEVER_USE[0] = this->_swiftMetadataInitializer_NEVER_USE[0];
return ro;
} else {
size_t size = sizeof(*this);
class_ro_t *ro = (class_ro_t *)memdup(this, size);
return ro;
}
}
};
类第一次初始化的时候类的方法,属性,成员变量属性协议等等都是存放在class_ro_t
中的,当程序运行的时候,需要将分类中的列表跟类初始的列表合并在一起的时,就会将class_ro_t中的列表和分类中的列表合并起来存放在class_rw_t中
realizeClass部分源码
static Class realizeClass(Class cls)
{
runtimeLock.assertWriting();
const class_ro_t *ro;
class_rw_t *rw;
Class supercls;
Class metacls;
bool isMeta;
if (!cls) return nil;
if (cls->isRealized()) return cls;
assert(cls == remapClass(cls));
// 最开始cls->data是指向ro的
ro = (const class_ro_t *)cls->data();
if (ro->flags & RO_FUTURE) {
// rw已经初始化并且分配内存空间
rw = cls->data(); // cls->data指向rw
ro = cls->data()->ro; // cls->data()->ro指向ro
cls->changeInfo(RW_REALIZED|RW_REALIZING, RW_FUTURE);
} else {
// 如果rw并不存在,则为rw分配空间
rw = (class_rw_t *)calloc(sizeof(class_rw_t), 1); // 分配空间
rw->ro = ro; // rw->ro重新指向ro
rw->flags = RW_REALIZED|RW_REALIZING;
// 将rw传入setData函数,等于cls->data()重新指向rw
cls->setData(rw);
}
}
类的初始信息本来其实是存储在class_ro_t
中的,并且ro本来是指向cls->data()的,也就是说bits.data()得到的是ro,但是在运行过程中创建了class_rw_t
,并将cls->data指向rw,同时将初始信息ro赋值给rw中的ro
。最后在通过setData(rw)设置data
。那么此时bits.data()得到的就是rw
,之后再去检查是否有分类,同时将分类的方法,属性,协议列表整合存储在class_rw_t的方法,属性及协议列表中
为什么会定义两个结构差不多的结构体来实现Class呢?
ro:read only,rw:read write,原因是class_ro_t
是编译期的产物,类源文件中的属性、方法、协议、成员变量在编译期就存在class_ro_t中,而class_rw_t
则是运行时的产物,class_rw_t的设计就是为了支撑Class的动态性,运行时将class_ro_t中的属性、协议、方法动态合并到对应的数据结构
cache_t
cache_t cache
:用来缓存曾经调用过的方法,可以提高方法的查找速度
cache_t是如何进行缓存
先来看一下cache_t的内部结构
struct cache_t {
#if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_OUTLINED
explicit_atomic<struct bucket_t *> _buckets;
explicit_atomic<mask_t> _mask;
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16
explicit_atomic<uintptr_t> _maskAndBuckets;
mask_t _mask_unused;
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_LOW_4
explicit_atomic<uintptr_t> _maskAndBuckets;
mask_t _mask_unused;
#else
#error Unknown cache mask storage type.
#endif
#if __LP64__
uint16_t _flags;
#endif
uint16_t _occupied;
public:
...
};
简化一下
struct cache_t {
struct bucket_t *_buckets; // 散列表 数组
mask_t _mask; // 散列表的长度 -1
mask_t _occupied; // 已经缓存的方法数量
};
bucket_t是以数组的方式储存方法列表的,看一下bucket_t内部结构
struct bucket_t {
private:
cache_key_t _key; // SEL作为Key
IMP _imp; // 函数的内存地址
};
bucket_t中存储着SEL和_imp,通过key->value的形式,以SEL为key
,函数实现的内存地址 _imp为value
来存储方法。bucket_t被称为散列表(哈希表)通过关键码值而直接进行访问的数据结构。
_mask
通过上面的分析我们知道_mask的值是散列表的长度减一,那么任何数通过与_mask进行按位与运算之后获得的值都会小于等于_mask,因此不会出现数组溢出的情况。
- 当第一次存储方法的时候,会创建具有4个空间的散列表,并将_mask的值置为散列表的长度减一,之后通过
SEL & mask
计算出方法存储的下标值,并将方法存储在散列表中(不会出现数组的溢出)。 - 当储存的方法超过散列表的3/4的时候,就会创建一个新的散列表,并将空间扩容为2倍。
- 如果方法很多,会出现SEL&mask得到的值是同一个下标值,就会调用cache_next函数向前一位比较存储。
- 查找的时候与存储时相同,也是通过SEL&mask计算出下标,直接到对应的存储位置取值,如果下表值中储存呢的key与查找的可以不同,就像前一位查找。
总结如图:
总结如图: