HXPerson *p = [[HXPerson alloc] init];
Class cls1 = p.class;
Class cls2 = object_getClass(p);
Class cls3 = [HXPerson class];
Class cls4 = object_getClass(cls2);
Class cls5 = object_getClass(cls3);
打印上面代码,我们可以看到能打印对应的类名字 那这个HXPerson到底是怎么样的,让我们从objc源码来探究下类的结构。
struct objc_class : objc_object {
// Class ISA;
Class superclass;
cache_t cache; // formerly cache pointer and vtable
class_data_bits_t bits; // class_rw_t * plus custom rr/alloc flags
首先我们可以看到一个对象类的头部,有三个成员变量,分别是Class superclass
,cache_t cache
,class_data_bits_t bits
,接着我们通过x/6gx
来打印这个类的结构,我们可以看到

我们打印第一个8字节,并得不到什么,打印第二个8字节发现是NSObject
,也就是我们HXPerson
的superclass
。那这样前面8自己是什么内容呢,我们通过注释可以了解到,superclass
前面还有一个隐藏的变量就是ISA
,那我们结合之前的学习,把这个内容和isa掩码做一个与操作。打印处理就是HXPerson。
接下来我们可以看到一个cache_t
,和class_data_bits_t
。这两个成员变量在内存中占了多少的空间呢,我们分别点进去看下对应的结构描述。
struct cache_t { //以下描述都是在64位机器下
struct bucket_t *_buckets; // 8字节,这是一个结构体指针,不是结构体。
//不用看内部的构造。如果是结构体就要看内部构造,然后一一计算。
//可以类比这里结构体的内存布局计算方式。
mask_t _mask; //4字节,在64位机器下,是一个uint32_t
mask_t _occupied; // 4字节,同上
struct class_data_bits_t {
// Values are the FAST_ flags above.
uintptr_t bits; // 8字节,无符号长整形。
最后我们可以得到类的内存布局。
struct objc_class : objc_object {
// Class ISA; //8字节
Class superclass; // 8字节
cache_t cache; // 16字节
class_data_bits_t bits; // 8字节 ?是否有问题
##cache_t
struct cache_t {
struct bucket_t *_buckets;
mask_t _mask;
mask_t _occupied;
public:
struct bucket_t *buckets();
mask_t mask();
mask_t occupied();
void incrementOccupied();
void setBucketsAndMask(struct bucket_t *newBuckets, mask_t newMask);
void initializeToEmpty();
mask_t capacity();
bool isConstantEmptyCache();
bool canBeFreed();
static size_t bytesForCapacity(uint32_t cap);
static struct bucket_t * endMarker(struct bucket_t *b, uint32_t cap);
void expand();
void reallocate(mask_t oldCapacity, mask_t newCapacity);
struct bucket_t * find(cache_key_t key, id receiver);
static void bad_cache(id receiver, SEL sel, Class isa) __attribute__((noreturn));
};
struct bucket_t {
private:
// IMP-first is better for arm64e ptrauth and no worse for arm64.
// SEL-first is better for armv7* and i386 and x86_64.
#if __arm64__
MethodCacheIMP _imp;
cache_key_t _key;
#else
cache_key_t _key;
MethodCacheIMP _imp;
#endif
public:
inline cache_key_t key() const { return _key; }
inline IMP imp() const { return (IMP)_imp; }
inline void setKey(cache_key_t newKey) { _key = newKey; }
inline void setImp(IMP newImp) { _imp = newImp; }
void set(cache_key_t newKey, IMP newImp);
};
cache_t
,顾名思义是一个和缓存有关系的结构,初步一看是和方法的缓存有关系,接下来我们来探究下这个cache_t
是怎么工作。
cache_t 方法缓存策略
我们通过incrementOccupied()
找到调用方cache_fill_nolock()
,接着找到再上一层cache_fill()
,然后在找上一层,我们可以看到有几个方法,其中有一个方法特别吸引人,就是lookUpImpOrForward()
,这样就和我们设想的对应上了,这个cache_t和方法缓存是有关系的.(相当于逆着找)

static void cache_fill_nolock(Class cls, SEL sel, IMP imp, id receiver)
这个方法是我们开始研究的主要对象.详细阅读代码后,我们可以整理一个流程.

class_data_bits_t
最后我们看一下类结构的最后一个元素class_data_bits_t
,它是做什么用的呢.
struct class_data_bits_t {// class_rw_t * plus custom rr/alloc flags
// Values are the FAST_ flags above.
uintptr_t bits; // 8字节,无符号长整形。
我们大概浏览其内部,就是一个无符号长整形变量以及一些方法,大家都说class_data_bits_t
存储类的方法、属性、协议等信息的地方,具体他是怎么样体现他的价值的呢?这些都是存在哪的呢?
乍一眼看去,这个bit和下面的第一个方法class_rw_t *data()
好像有点关系,我们点进去看下这个class_rw_t:
struct class_rw_t {
// Be warned that Symbolication knows the layout of this structure.
uint32_t flags;
uint32_t version;
const class_ro_t *ro;
method_array_t methods;
property_array_t properties;
protocol_array_t protocols;
Class firstSubclass;
Class nextSiblingClass;
char *demangledName;
这样两者的关系好像就联系上了。我们试着用lldb打印下内存信息看。
把bits
强转类型为 class_data_bits_t *
,获取 bits -> data()
返回的 class_rw_t *
并打印显示如下结果。

接着我们打印下class_rw_t
的ro
属性,里面包含ivars
,properties
等信息

接着我们来试着找一下里面的属性(HXPerson定义了一些name,age等属性),通过直接打印得到:

ro.basePropertyies
是一个property_list_t
,我们来看下这个property_list_t
是怎么实现的
struct property_list_t : entsize_list_tt<property_t, property_list_t, 0> {
};
struct entsize_list_tt {
uint32_t entsizeAndFlags;
uint32_t count;
Element first;
uint32_t entsize() const {
return entsizeAndFlags & ~FlagMask;
}
uint32_t flags() const {
return entsizeAndFlags & FlagMask;
}
Element& getOrEnd(uint32_t i) const {
assert(i <= count);
return *(Element *)((uint8_t *)&first + i*entsize());
}
Element& get(uint32_t i) const {
assert(i < count);
return getOrEnd(i);
}
里面有个get
方法,我们试着调用下看

age
方法

同理,我们可以得到方法列表,协议列表等。
总结和问题
通过上述看源代码,lldb打印可以得到Class的大概结构,只是大概了解。当然在看源代码时还有很多细节疑问。类的结构还要再复习阅读。
1.cache_t章节:cache_getImp是怎么实现的。
2.cache_t章节:在开辟buckets为什么有(uintptr_t)(mask_t)(newCapacity-1) == newCapacity-1
这样的判断。
3.cache_t章节:emptyBucketsForCapacity()
空桶的判断有这么一道处理,还没太理解其中的用意。