-
准备工作
- objc4-781可编译源码
- 创建一个类随意创建几个方法如下
#import <Foundation/Foundation.h> NS_ASSUME_NONNULL_BEGIN @interface TDPerson : NSObject @property (nonatomic, copy) NSString *lgName; @property (nonatomic, strong) NSString *nickName; -(void)sayHello; - (void)sayCode; - (void)sayMaster; - (void)sayNB; + (void)sayHappy; @end NS_ASSUME_NONNULL_END #import "TDPerson.h" @implementation TDPerson - (void)sayHello{ NSLog(@"LGPerson say : %s",__func__); } - (void)sayCode{ NSLog(@"LGPerson say : %s",__func__); } - (void)sayMaster{ NSLog(@"LGPerson say : %s",__func__); } - (void)sayNB{ NSLog(@"LGPerson say : %s",__func__); } + (void)sayHappy{ NSLog(@"LGPerson say : %s",__func__); } @end -
通过源码探索cache所包含的内容
struct objc_class : objc_object { // Class ISA; Class superclass; cache_t cache; // formerly cache pointer and vtable class_data_bits_t bits; } #define CACHE_MASK_STORAGE_OUTLINED 1 #define CACHE_MASK_STORAGE_HIGH_16 2 #define CACHE_MASK_STORAGE_LOW_4 3 #if defined(__arm64__) && __LP64__ //真机64位 #define CACHE_MASK_STORAGE CACHE_MASK_STORAGE_HIGH_16 #elif defined(__arm64__) && !__LP64__ //真32位 #define CACHE_MASK_STORAGE CACHE_MASK_STORAGE_LOW_4 #else //模拟器/mac #define CACHE_MASK_STORAGE CACHE_MASK_STORAGE_OUTLINED #endif struct cache_t { #if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_OUTLINED //模拟器/真机中的cache _buckets和_mask是分开的 //explicit_atomic 原子性,保证增删改查时候的线程安全 //_buckets 查看源码可知里面存储的是sel和imp explicit_atomic<struct bucket_t *> _buckets; explicit_atomic<mask_t> _mask; ... //其他都是写掩码 省略 #elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16 //真机将_mask和_buckets写在一起为了节省空间 explicit_atomic<uintptr_t> _maskAndBuckets; mask_t _mask_unused; ... //其他都是写掩码 省略 #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; ... //其他都是写掩码 省略 } 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 }- sel:方法编号(其实就是方法名)
- imp:函数指针地址
-
查找cache中的sel和imp
- 通过源码查找
应为buckets内bucket是连续存储的所以可以通过指针加一的方式找到下一个bucket如下
- 脱离源码查找
对象在底层其实就是一个结构体,oc代码的结果 还是要翻译成c/c++/汇编语言,所以干脆可以将底层结构体搬出来运行如下:
看运行结果打印_occupied 和 _mask,_sel,_imp:#import <Foundation/Foundation.h> #import "TDPerson.h" #import <objc/runtime.h> typedef uint32_t mask_t; // x86_64 & arm64 asm are less efficient with 16-bits struct td_bucket_t { SEL _sel; IMP _imp; }; struct td_cache_t { struct td_bucket_t * _buckets; mask_t _mask; uint16_t _flags; uint16_t _occupied; }; struct td_class_data_bits_t { uintptr_t bits; }; struct td_objc_class { Class ISA; Class superclass; struct td_cache_t cache; // formerly cache pointer and vtable struct td_class_data_bits_t bits; // class_rw_t * plus custom rr/alloc flags }; int main(int argc, const char * argv[]) { @autoreleasepool { TDPerson *p = [TDPerson alloc]; Class pClass = [TDPerson class]; // objc_clas // [p say1]; // [p say2]; // [p say3]; [p say4]; struct td_objc_class *td_pClass = (__bridge struct td_objc_class *)(pClass); NSLog(@"%hu - %u",td_pClass->cache._occupied,td_pClass->cache._mask); for (mask_t i = 0; i<td_pClass->cache._mask; i++) { // 打印获取的 bucket struct td_bucket_t bucket = td_pClass->cache._buckets[i]; NSLog(@"%@ - %p",NSStringFromSelector(bucket._sel),bucket._imp); } NSLog(@"Hello, World!"); } return 0; }发现低啊用两个方法的时候打印cache里面的缓存没问题是两个方法,但是调用三个方法的时候就会出现问题,缓冲中只有第三个方法,还有一个点事_occupied和_mask分别是什么
- 通过源码查找
-
源码分析_occupied
- 首先从cache_t结构体里面看发现有如下方法
- 在进到incrementOccupied方法内查看
发现是一个occupied自增长函数
- 全局搜索该函数找到如下位置(这里也可以冲方法调用开始一步一步往下跟一样能找到)
发现是在cache_t insert方法内找到该方法调用,然后又是在插入bucket的时候调用一次,所以很明确occupied是bucket的数量
- 首先从cache_t结构体里面看发现有如下方法
-
_mask是什么?
_mask是指掩码数据,用于在哈希算法或者哈希冲突算法中计算哈希下标,其中mask 等于capacity - 1
-
上文中打印occupied数值问题
上文的案例可以看到调用两个方法的时候打印occupied是2,没问题应为调用了两个方法,但是当调用第三个方法的时候就发现问题了,发现occupied打印的是1,按照上文所说应该是3啊,接下来分析一下插入缓存的核心流程insert原理- insert源码分析
从这段代码可以知道方法插入计算下标的时候可能会出现哈希冲突,如果出现哈希冲突就需要重新计算方法的下标,所以在遍历buckets的时候有时候方法打印的顺序不一定就和方法调用的顺序一致注意:init和属性赋值也会插入cache,init本身就是一个方法,属性赋值会触发set方法所以也会存到cache 2. reallocate源码分析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 //第一步计算newOccupied 这里若果没有init和属性赋值(会有一个set方法)的情况下并且是第一次调用方法则occupied() = 0 newOccupied = 1 mask_t newOccupied = occupied() + 1; //第二步得到当前容量 unsigned oldCapacity = capacity(), capacity = oldCapacity; //slowpath小概率事件isConstantEmptyCache buckets是空的情况下 创建缓存 if (slowpath(isConstantEmptyCache())) { // Cache is read-only. Replace it. //INIT_CACHE_SIZE = (1 << INIT_CACHE_SIZE_LOG2), INIT_CACHE_SIZE_LOG2 =2 //其实就是初始化容量是4 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. //如果newOccupied + CACHE_END_MARKER = newOccupied + 1的数量小于总容量的3/4的话不需要做处理 //直接走下面的插入流程 } else { //大于总容量的3/4则进行扩容(原来容量的两倍) 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; //通过哈希计算bucket_t下标 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<Atomic, Encoded>(sel, imp, cls); return; } if (b[i].sel() == sel) { //如果当前位置下存储的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); }从这里可以发现当需要扩容的时候会将以前存储的方法清理掉,所以上述调用到say3的时候你会发现say1和say2不见了 3. insert流程ALWAYS_INLINE void cache_t::reallocate(mask_t oldCapacity, mask_t newCapacity, bool freeOld) { bucket_t *oldBuckets = buckets(); //得到旧的buckets bucket_t *newBuckets = allocateBuckets(newCapacity); //创建一个新的buckets,此时是个临时的buckets // Cache's old contents are not propagated. // This is thought to save cache memory at the cost of extra cache fills. // fixme re-measure this ASSERT(newCapacity > 0); ASSERT((uintptr_t)(mask_t)(newCapacity-1) == newCapacity-1); //将临时的buckets存到缓存中 setBucketsAndMask(newBuckets, newCapacity - 1); if (freeOld) { //释放老的bucket /** static void cache_collect_free(bucket_t *data, mask_t capacity) { #if CONFIG_USE_CACHE_LOCK cacheUpdateLock.assertLocked(); #else runtimeLock.assertLocked(); #endif if (PrintCaches) recordDeadCache(capacity); _garbage_make_room (); // 创建垃圾回收空间 garbage_byte_size += cache_t::bytesForCapacity(capacity); garbage_refs[garbage_count++] = data; //将传入的buckets向后添加 cache_collect(false); //开始释放 } */ cache_collect_free(oldBuckets, oldCapacity); } }