iOS 底层探索 文章汇总
目录
- 一、前言
- 三、cache_t的工作原理
- 三、cache_t的工作原理
- 四、总结
一、前言
上一篇文章iOS 类的结构分析中我们分析了类的底层结构,知道了类中存在cache_t cache
。那么cache
中到底缓存了哪些数据,cache_t
的底层结构又是怎样的呢?这篇文章我们就一起来分析类的底层结构到底是什么。
类的底层代码如下:
struct objc_class : objc_object {
// Class ISA; // 8字节
Class superclass; // 8字节
cache_t cache; // formerly cache pointer and vtable 16字节
class_data_bits_t bits; // class_rw_t * plus custom rr/alloc flags
....省略后面的代码.....
}
二、cache_t的底层结构
首先通过底层代码我们来看一下cache_t
的底层是如何定义的:
struct cache_t {
#if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_OUTLINED -- macOS使用
explicit_atomic _buckets;
explicit_atomic _mask; // typedef uint32_t mask_t;
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16 -- iOS使用
explicit_atomic _maskAndBuckets;// arm64中mask和Buckets存在一起
mask_t _mask_unused;
// How much the mask is shifted by.
static constexpr uintptr_t maskShift = 48;
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 --其他设备使用
explicit_atomic _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;//占用
public:
static bucket_t *emptyBuckets();
struct bucket_t *buckets();
mask_t mask();
mask_t occupied();
void incrementOccupied();
void setBucketsAndMask(struct bucket_t *newBuckets, mask_t newMask);
void initializeToEmpty();
unsigned capacity();//容量
bool isConstantEmptyCache();
bool canBeFreed();
....省略后面的代码.....
};
1. 针对x86架构
的macOS
分析可知主要有以下字段:
- explicit_atomic
_buckets
; - explicit_atomic
_mask
; - uint16_t
_flags
; - uint16_t
_occupied
;
各变量的主要作用:
-
_buckets
:数组,是bucket_t结构体
的数组,bucket_t
是用来存放方法的SEL内存地址
和IMP
的。 -
_mask
的大小是数组大小 - 1
,用作掩码。(因为这里维护的数组大小都是2的整数次幂,所以_mask
的二进制位000011, 000111, 001111)刚好可以用作hash取余数
的掩码。刚好保证相与后不超过缓存大小。 -
_occupied
是当前已缓存的方法数。即数组中已使用了多少位置。
2. cache_t
中主要的方法:
- struct bucket_t *
buckets()
; //获取buckets - mask_t
mask()
; //获取mask - mask_t
occupied()
; //获取occupied - void
incrementOccupied()
;//增加occupied - void
setBucketsAndMask(struct bucket_t *newBuckets, mask_t newMask)
;//设置Buckets和Mask
bucket_t
底层定义如下:
struct bucket_t {
private:
explicit_atomic _sel;
explicit_atomic _imp;
....省略后面的代码.....
public:
inline SEL sel() const { return _sel.load(memory_order::memory_order_relaxed); }
inline IMP imp(Class cls) const {
. . .
return (IMP)imp;
}
....省略后面的代码.....
}
bucket_t
中只有_sel
和_imp
,所以类中的cache
就是用来缓存方法
的。
三、cache_t的工作原理
如果要探索cache_t
的工作原理,我们需要从cache
的insert
方法开始探索:
ALWAYS_INLINE
void cache_t::insert(Class cls, SEL sel, IMP imp, id receiver)
{
#if CONFIG_USE_CACHE_LOCK
cacheUpdateLock.assertLocked();
#else
runtimeLock.assertLocked();
#endif
ASSERT(sel != 0 && cls->isInitialized());
// Use the cache as-is if it is less than 3/4 full
mask_t newOccupied = occupied() + 1;
unsigned oldCapacity = capacity(), capacity = oldCapacity;
if (slowpath(isConstantEmptyCache())) {
// Cache is read-only. Replace it.
if (!capacity) capacity = INIT_CACHE_SIZE;
reallocate(oldCapacity, capacity, /* freeOld */false);
}
else if (fastpath(newOccupied + CACHE_END_MARKER <= capacity / 4 * 3)) { // 4 3 + 1 bucket cache_t
// Cache is less than 3/4 full. Use it as-is.
}
else {
capacity = capacity ? capacity * 2 : INIT_CACHE_SIZE; // 扩容两倍 4
if (capacity > MAX_CACHE_SIZE) {
capacity = MAX_CACHE_SIZE;
}
reallocate(oldCapacity, capacity, true); // 内存 扩容完毕
}
bucket_t *b = buckets();
mask_t m = capacity - 1;
mask_t begin = cache_hash(sel, m);
mask_t i = begin;
// Scan for the first unused slot and insert there.
// There is guaranteed to be an empty slot because the
// minimum size is 4 and we resized at 3/4 full.
do {
if (fastpath(b[i].sel() == 0)) {
incrementOccupied();
b[i].set(sel, imp, cls);
return;
}
if (b[i].sel() == sel) {
// The entry was added to the cache by some other thread
// before we grabbed the cacheUpdateLock.
return;
}
} while (fastpath((i = cache_next(i, m)) != begin));
cache_t::bad_cache(receiver, (SEL)sel, cls);
}
insert
的工作流程如下:
检查
Cache
容量
I. 如果Cache
为空就分配内存并初始化
II. 如果最新一个插入后占用小于等于
总容量的3/4
就不扩容
III. 当总容量大于3/4
就将当前容量扩展两倍
,之前缓存的内存直接释放,不会将之前缓存的方法存到新内存中插入最新的方法缓存
I. 获取到buckets
II. 设置初始插入index
为cache_hash(sel, m); ------> return sel & mask
III. 循环遍历buckets
i. 如果当前bucket
中没有sel
就存在当前位置并退出方法
ii. 如果当前bucket
中存的sel
和即将要缓存的sel
一致就退出方法
iii. 下一个缓存的index cache_next(i, m) ------> return (i+1) & mask
iiii. 如果下一个缓存的index
和初始index
相等就退出循环
IIII. 如果缓存sel
没有存储就执行cache_t::bad_cache(receiver, (SEL)sel, cls);
方法
sel & mask = index: index 一定是<=_ mask
-
底层代码分析图:
Cache
的整体流程在objc-cache.mm
文件中可以看到:
* Cache readers (PC-checked by collecting_in_critical())
* objc_msgSend*
* cache_getImp
*
* Cache writers (hold cacheUpdateLock while reading or writing; not PC-checked)
* cache_fill (acquires lock)
* cache_expand (only called from cache_fill)
* cache_create (only called from cache_expand)
* bcopy (only called from instrumented cache_expand)
* flush_caches (acquires lock)
* cache_flush (only called from cache_fill and flush_caches)
* cache_collect_free (only called from cache_expand and cache_flush)
四、总结
-
OC
中实例方法
缓存在类
上面,类方法
缓存在元类
上面。 - 即使
子类
调用父类
的方法,那么父类
的这个方法也不会缓存到子类
中。子类和父类各种缓存自己的方法
。 -
cache_t
缓存会提前进行扩容防止溢出
。 -
方法缓存
是为了最大化的提高程序的执行效率
。 - 苹果在方法缓存这里用的是
开放寻址法
来解决哈希冲突
。 - 通过
cache_t
我们可以进一步延伸去探究objc_msgSend
,因为查找方法缓存是属于objc_msgSend
查找方法实现的快速流程
。
参考
iOS-底层原理 11:objc_class 中 cache 原理分析