前言
上一章我们主要探索了类的对象本质和对象与类的关系,这一章我们主要来探索了类的结构,了解一下类里面存储的到底是什么东西,有什么作用。
类的结构
Class
我们要了解类,那我们就要研究一下Class,我们去源码那里看看Class里面存储了什么东西。
typedef struct objc_class *Class;
我们可以看到Class是一个对象,这么说类也是一个对象,我们一般叫类对象。
objc_class
那我们再点击进去看下objc_class是什么?
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_rw_t *data() {
return bits.data();
}
void setData(class_rw_t *newData) {
bits.setData(newData);
}
/**省略几百行内容*/
}
这里的代码我们是有点熟悉的了,前面的几章也有提及的到。
那这里继承着 objc_object,我们先看下父类存放着什么东西。
struct objc_object {
private:
isa_t isa;
}
原来父类存放着就是isa指针。
上2章我们已经分析过isa和superclass。那这里我们主要看下cache_t和class_data_bits_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;
// How much the mask is shifted by.
static constexpr uintptr_t maskShift = 48;
// Additional bits after the mask which must be zero. msgSend
// takes advantage of these additional bits to construct the value
// `mask << 4` from `_maskAndBuckets` in a single instruction.
static constexpr uintptr_t maskZeroBits = 4;
// The largest mask value we can store.
static constexpr uintptr_t maxMask = ((uintptr_t)1 << (64 - maskShift)) - 1;
// The mask applied to `_maskAndBuckets` to retrieve the buckets pointer.
static constexpr uintptr_t bucketsMask = ((uintptr_t)1 << (maskShift - maskZeroBits)) - 1;
// Ensure we have enough bits for the buckets pointer.
static_assert(bucketsMask >= MACH_VM_MAX_ADDRESS, "Bucket field doesn't have enough bits for arbitrary pointers.");
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_LOW_4
// _maskAndBuckets stores the mask shift in the low 4 bits, and
// the buckets pointer in the remainder of the value. The mask
// shift is the value where (0xffff >> shift) produces the correct
// mask. This is equal to 16 - log2(cache_size).
explicit_atomic<uintptr_t> _maskAndBuckets;
mask_t _mask_unused;
static constexpr uintptr_t maskBits = 4;
static constexpr uintptr_t maskMask = (1 << maskBits) - 1;
static constexpr uintptr_t bucketsMask = ~maskMask;
#else
#error Unknown cache mask storage type.
#endif
#if __LP64__
uint16_t _flags;
#endif
uint16_t _occupied;
}
_buckets:为结构体指针,是一个散列表。
_mask:为当前最大可存储容量。
_occupied:为缓存方法的数量。
buckets
我们看一下buckets里面到底存放着什么东西。
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__
explicit_atomic<uintptr_t> _imp;
explicit_atomic<SEL> _sel;
#else
explicit_atomic<SEL> _sel;
explicit_atomic<uintptr_t> _imp;
#endif
bucket_t里面有_imp和_sel属性。明显,这里就是用来存储缓存的方法。
cache_fill
我们调用对象的方法后,就会走objc_msgSend流程,就会进来cache_fill这个方法。
void cache_fill(Class cls, SEL sel, IMP imp, id receiver)
{
runtimeLock.assertLocked();
#if !DEBUG_TASK_THREADS
// Never cache before +initialize is done
if (cls->isInitialized()) {
cache_t *cache = getCache(cls);
#if CONFIG_USE_CACHE_LOCK
mutex_locker_t lock(cacheUpdateLock);
#endif
cache->insert(cls, sel, imp, receiver);
}
#else
_collecting_in_critical();
#endif
}
这里的意思是如果类已经初始化,然后会获取cache,调用cache->insert方法。
我们直接在代码里面进行分析。
void cache_t::insert(Class cls, SEL sel, IMP imp, id receiver)
{
ASSERT(sel != 0 && cls->isInitialized());
// Use the cache as-is if it is less than 3/4 full
// newOccupied为新的方法缓存数量 = 缓存方法数量 + 1。
mask_t newOccupied = occupied() + 1;
// 获取当前容量,为mask+1
unsigned oldCapacity = capacity(), capacity = oldCapacity;
// 如果缓存是空的
if (slowpath(isConstantEmptyCache())) {
// Cache is read-only. Replace it.
//如果容量capacity为0 ,则初始化为 4
if (!capacity) capacity = INIT_CACHE_SIZE;
// 优化容量空间,这里传false
reallocate(oldCapacity, capacity, /* freeOld */false);
}
//如果newOccupied < 容量的4分之三,代表存储空间充足,不用处理
else if (fastpath(newOccupied + CACHE_END_MARKER <= capacity / 4 * 3)) {
// Cache is less than 3/4 full. Use it as-is.
}
// 超过3/4
else {
// 对当前容量进行扩容,为原来的2倍。
capacity = capacity ? capacity * 2 : INIT_CACHE_SIZE;
// 容量大于 16, 则为16,即最大为16
if (capacity > MAX_CACHE_SIZE) {
capacity = MAX_CACHE_SIZE;
}
// 优化容量空间,这里传true
reallocate(oldCapacity, capacity, true);
}
// 下面是根据散列表是否有该方法的缓存,没有存在该方法则插入缓存。
bucket_t *b = buckets();
mask_t m = capacity - 1;
mask_t begin = cache_hash(sel, m);
mask_t i = begin;
do {
if (fastpath(b[i].sel() == 0)) {
incrementOccupied();
b[i].set<Atomic, Encoded>(sel, imp, cls);
return;
}
if (b[i].sel() == sel) {
return;
}
} while (fastpath((i = cache_next(i, m)) != begin));
cache_t::bad_cache(receiver, (SEL)sel, cls);
}
这里面有2处调用到reallocate,1个传true,1个传false。我们先看看里面做了什么处理。
void cache_t::reallocate(mask_t oldCapacity, mask_t newCapacity, bool freeOld)
{
bucket_t *oldBuckets = buckets();
// 开辟空间
bucket_t *newBuckets = allocateBuckets(newCapacity);
ASSERT(newCapacity > 0);
ASSERT((uintptr_t)(mask_t)(newCapacity-1) == newCapacity-1);
// 设置Buckets和mask,mask为新容量-1。
setBucketsAndMask(newBuckets, newCapacity - 1);
if (freeOld) {
//为true,则清空缓存信息
cache_collect_free(oldBuckets, oldCapacity);
}
}
下面添加一下容量capacity与mask的关系代码。
unsigned cache_t::capacity()
{
return mask() ? mask()+1 : 0;
}
chche方法总结
到这里,我们总结一下上面做了什么。
-
先初始化缓存空间。有1个调用方法进来,先给方法缓存数
newOccupied+1操作,用来和容量capacity进行对比。 -
先判断缓存是否为空,一开始缓存是空的,我们初始化容量
capacity为4。这时候,Occupied方法占用数不会超过我们初始化容量capacity的4分之3,散列表buckets则存储该方法。 -
后面陆续再调用几个方法后,缓存不为空,方法缓存数
Occupied超过容量我们初始化容量capacity的4分之三,则对容量capacity进行扩容,为原来的2倍且最大为16。同时释放已有的缓存信息。重新存储新的方法。 -
存储方法的时候,先散列表进行遍历,没有找到缓存方法,则方法缓存数
Occupied加1,如果散列表里面本来就有,就不作处理。
class_data_bits_t
struct class_data_bits_t {
friend objc_class;
uintptr_t bits;
public:
class_rw_t* data() const {
return (class_rw_t *)(bits & FAST_DATA_MASK);
}
void setData(class_rw_t *newData)
{
ASSERT(!data() || (newData->flags & (RW_REALIZING | RW_FUTURE)));
uintptr_t newBits = (bits & ~FAST_DATA_MASK) | (uintptr_t)newData;
atomic_thread_fence(memory_order_release);
bits = newBits;
}
}
结合一开始的bits.data()和bits.setdata()我们就可以就可以get和setclass_rw_t。
class_rw_t
struct class_rw_t {
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 *>;
const ro_or_rw_ext_t get_ro_or_rwe() const {
return ro_or_rw_ext_t{ro_or_rw_ext};
}
}
这里的ro_or_rw_ext,编译时值是class_ro_t,编译后类实现完成时的值是 class_rw_ext_t,而编译时的 class_ro_t 作为 class_rw_ext_t 的 const class_ro_t *ro 成员变量保存。
class_rw_ext_t
类中的属性、方法还有遵循的协议等信息都保存在 class_rw_ext_t中。
class_rw_ext_t在运行时会把class_ro_t的内容拷贝过来作为属性,然后再将当前类的分类的这些属性、方法等拷贝到其中。
当然实际访问类的方法、属性等也都是访问的class_rw_t中的内容。
struct class_rw_ext_t {
const class_ro_t *ro;
method_array_t methods;
property_array_t properties;
protocol_array_t protocols;
char *demangledName;
uint32_t version;
};
这里的class_rw_ext_t 中存储信息有以下:
ro: class_ro_t。methods:方法列表,包括分类方法。properties:属性列表。protocols:协议列表。demangledName:所属类名。
class_ro_t
class_ro_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;
}
这里的class_ro_t 中存储信息有以下:
baseMethodList: 方法列表。baseProperties:属性列表 。baseProtocols:协议列表。ivars:成员变量列表。
bits总结
从上面分析可以看到类对象的主要信息, 都存在了 class_rw_ext_t 和 class_ro_t 两个结构体中。
总结
通过上面的分析,我们就很清楚知道类的结构有isa指针,父类superClass,缓存方法cache,以及bits(包含方法列表,属性列表,协议列表,成员变量列表)等信息。
那下一章,我们就详细聊聊方法的本质是什么,以及调用的流程是怎样的。