AI智能
改变未来

iOS OC类原理二

iOS OC类原理二

  • 前言:
  • 1. `cache_t cache LLDB` 简单分析
  • 2.`cache_t cache` 流程源码分析
  • `cache_fill_nolock`详细流程:

前言:

上一篇探索了

属性 成员变量 方法

中是如何存储的,即存储在

class_ro_t *ro

中,上一篇中提到为什么在

rw

中也能打印相应的

属性 方法

呢?

因为

rw

中的

属性 方法

在编译期是没有的,是在运行时从

ro

copy

赋值到

rw

中。

struct objc_class : objc_object {// Class ISA; // 8Class superclass; // 8cache_t cache;    // 16 不是8         // formerly cache pointer and vtableclass_data_bits_t bits;    // class_rw_t * plus custom rr/alloc flagsclass_rw_t *data() {return bits.data();}···}

的源码中不难看出前两个

成员

分别是

isa

superclass

,上一篇我们探索了

属性 成员变量 方法

bits

中的存储,那么

cache_t cache

中存储的是什么呢 ?

cache_t cache

源码:

struct cache_t {struct bucket_t *_buckets; // 8mask_t _mask;  // 4mask_t _occupied; // 4public:struct bucket_t *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__MethodCacheIMP _imp;cache_key_t _key;#elsecache_key_t _key;MethodCacheIMP _imp;#endifpublic: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

中存储的是

方法

的缓存。

1.

cache_t cache LLDB

简单分析

首先创建一个

,代码如下:

@interface LGPerson : NSObject{NSString *hobby;}@property (nonatomic, copy) NSString *name;- (void)sayHello;- (void)sayCode;- (void)sayMaster;- (void)sayNB;+ (void)sayHappy;@end#import \"LGPerson.h\"@implementation LGPerson- (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__);}@endint main(int argc, const char * argv[]) {@autoreleasepool {LGPerson *person = [LGPerson alloc];Class pClass = [LGPerson class];// cache_t 为什么没有 - 第一次[person sayHello];[person sayCode];[person sayNB];}}

通过打印

cache_t cache

,发现方法缓存确实存在

cache_t cache

中(有时候系统可能会出现问题,打印出

cache_t cache

中的

_key

_imp

为空,多运行打印几次就ok)。

2.

cache_t cache

流程源码分析

通过上面的打印查看了

cache_t cache

的缓存内容,接下来查看源码分析一下

cache_t cache

的具体流程:

首先查看

cache_t

中容量

mask_t capacity()

的实现:

mask_t capacity()

的实现:

mask_t cache_t::capacity(){return mask() ? mask()+1 : 0;}mask_t cache_t::mask(){return _mask;}

在实现中对

cache_t

_mask

进行

+1

,那么什么是调用对这个

capacity()

的呢?

通过搜索源码查看在

expand()

方法中调用:

void cache_t::expand()  // 扩容{cacheUpdateLock.assertLocked();uint32_t oldCapacity = capacity();uint32_t newCapacity = oldCapacity ? oldCapacity*2 : INIT_CACHE_SIZE;if ((uint32_t)(mask_t)newCapacity != newCapacity) {// mask overflow - can\'t grow further// fixme this wastes one bit of masknewCapacity = oldCapacity;}reallocate(oldCapacity, newCapacity);}

扩容 expand()

方法是在

cache_fill_nolock

方法中调用

static void cache_fill_nolock(Class cls, SEL sel, IMP imp, id receiver){cacheUpdateLock.assertLocked();// Never cache before +initialize is doneif (!cls->isInitialized()) return;// Make sure the entry wasn\'t added to the cache by some other thread// before we grabbed the cacheUpdateLock.if (cache_getImp(cls, sel)) return;cache_t *cache = getCache(cls);cache_key_t key = getKey(sel);// Use the cache as-is if it is less than 3/4 fullmask_t newOccupied = cache->occupied() + 1;mask_t capacity = cache->capacity();if (cache->isConstantEmptyCache()) {// Cache is read-only. Replace it.cache->reallocate(capacity, capacity ?: INIT_CACHE_SIZE);}else if (newOccupied <= capacity / 4 * 3) {// Cache is less than 3/4 full. Use it as-is.}else {// Cache is too full. Expand it.cache->expand();}// 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.bucket_t *bucket = cache->find(key, receiver);if (bucket->key() == 0) cache->incrementOccupied();bucket->set(key, imp);}

由此我们找到了这个方法调用的入口,接下来我们断点调试分析一下这个详细的流程:

cache_fill_nolock

详细流程:

cache_fill_nolock

方法中:

1.先从 cache 中获取 imp ,获取到直接 returnif (cache_getImp(cls, sel)) return;2.获取 cache 和 key,第一次调用 sayHello 方法,cache 中_mask 和 _occupied 为0

3. 对 cache 的 occupied + 1, 并获取容量 capacity (此时为0),4. 判断是否是 isConstantEmptyCache ,如果是 EmptyCache,调用cache->reallocate(capacity, capacity ?: INIT_CACHE_SIZE)跳转到第 7 步;INIT_CACHE_SIZE 为:1 << 2 = 4enum {INIT_CACHE_SIZE_LOG2 = 2,INIT_CACHE_SIZE      = (1 << INIT_CACHE_SIZE_LOG2)}5. 判断桶子 Buckets 的占用量是否是 小于等于 容量的3/4即:newOccupied <= capacity / 4 * 3,小于3/4 直接跳转到  第8步;6. 当桶子 Buckets 的占用量达到临界点时,执行扩容 cache->expand();expand() 方法中:6.1 获取 oldCapacity = capacity()  // 4;newCapacity = oldCapacity ? oldCapacity*2 : INIT_CACHE_SIZE;  // 86.2 reallocate(oldCapacity, newCapacity)即:reallocate(4, 8),跳转到 第7步;7. reallocate(mask_t oldCapacity, mask_t newCapacity) 传入一个oldCapacity = 0,newCapacity = 1 << 2 = 47.1 先获取 freeOld 标识,是否释放旧缓存,bool freeOld = canBeFreed()即:bool cache_t::canBeFreed(){return !isConstantEmptyCache();}此时,没有缓存 cache, 所以 freeOld 为false7.2 创建一个新的 bucket_t *newBuckets,开辟4个位置7.3 用新容量 newCapacity - 1,对创建的 newBuckets 进行设置setBucketsAndMask(newBuckets, newCapacity - 1);7.4 判断 释放标识 freeOld,true: 释放旧的 oldBuckets 和 oldCapacity即:cache_collect_free(oldBuckets, oldCapacity);false:不执行此时 freeOld 为 false8. 从 cache 中根据 key 查找 合适 Buckets8.1 获取 buckets,获取 mask = 3 (sort)bucket_t *b = buckets();mask_t m = mask();8.2 通过对 k (对 sel 哈希的到的 cache_key_t key = getKey(sel)) 和 m 进行哈希得到下标mask_t begin = cache_hash(k, m);static inline mask_t cache_hash(cache_key_t key, mask_t mask){return (mask_t)(key & mask);}通过 sel 和 mask 的位运算,计算出一个合理的 begin,就是哈希的下标8.3 通过 begin, do...while 循环,查找 bucket_t。查找到就返回,查找不到返回 bad_cache9. 桶子 bucket 查找到以后,可以占用,对 _occupied++。10. 把 key 和 imp 保存在桶子里  bucket->set(key, imp)。

到此,cache_t cache中的缓存存储流程分析完成。

简单的总结,

1. 先查找缓存,缓存命中直接返回,2. 当没有缓存时,开辟新的缓存并初始化,然后查找桶子 _buckets,然后 _occupied ++,然后存储 set(key, imp);3. 当有缓存,并小于容量的3/4时,直接查找桶子 _buckets,然后 _occupied ++,然后存储 set(key, imp);4. 当有缓存,大于容量的3/4时,扩容到二倍,查找桶子 _buckets,然后 _occupied ++,然后存储 set(key, imp);
  • 点赞1
  • 收藏
  • 分享
  • 文章举报

亮亮不想说话发布了18 篇原创文章 · 获赞 10 · 访问量 267私信关注

赞(0) 打赏
未经允许不得转载:爱站程序员基地 » iOS OC类原理二