iOS OC类原理二
2020-04-20 15:02
836 查看
iOS OC类原理二
前言:
上一篇探索了
属性 成员变量 方法在
类中是如何存储的,即存储在
class_ro_t *ro中,上一篇中提到为什么在
rw中也能打印相应的
属性 方法呢?
因为
rw中的
属性 方法在编译期是没有的,是在运行时从
ro中
copy赋值到
rw中。
struct objc_class : objc_object { // Class ISA; // 8 Class superclass; // 8 cache_t cache; // 16 不是8 // 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(); } ··· }
从
类的源码中不难看出前两个
成员分别是
isa和
superclass,上一篇我们探索了
属性 成员变量 方法在
bits中的存储,那么
cache_t cache中存储的是什么呢 ?
cache_t cache源码:
struct cache_t { struct bucket_t *_buckets; // 8 mask_t _mask; // 4 mask_t _occupied; // 4 public: 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; #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中存储的是
方法的缓存。
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__); } @end int 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 mask newCapacity = 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 done if (!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 full mask_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 ,获取到直接 return if (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 = 4 enum { 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; // 8 6.2 reallocate(oldCapacity, newCapacity) 即:reallocate(4, 8),跳转到 第7步; 7. reallocate(mask_t oldCapacity, mask_t newCapacity) 传入一个oldCapacity = 0, newCapacity = 1 << 2 = 4 7.1 先获取 freeOld 标识,是否释放旧缓存,bool freeOld = canBeFreed() 即:bool cache_t::canBeFreed() { return !isConstantEmptyCache(); } 此时,没有缓存 cache, 所以 freeOld 为false 7.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 为 false 8. 从 cache 中根据 key 查找 合适 Buckets 8.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_cache 9. 桶子 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
- 收藏
- 分享
- 文章举报
相关文章推荐
- iOS开发消息推送原理
- IOS对象关系映射(ORM)之coreData框架的学习——(二)原理剖析及使用步骤详解
- iOS 和 Android 的后台推送原理各是什么?有什么区别?
- iOS开发 - 微信扫描二维码登录网页的原理
- iOS中block深入原理研究
- IOS 基于APNS消息推送原理与实现(JAVA后台)
- IOS 消息推送原理及实现总结
- iOS-关于应用/视图的生命周期及程序启动原理小结
- Unity IOS Android 消息推送原理分析
- iOS - 理解内存管理(二)- 循环引用的原理及检测方法、ARC
- iOS底层原理-KVO本质探究
- iOS加固保护原理
- iOS 基础篇1—程序启动原理和UIApplication
- iOS 消息推送及本地通知,原理解析
- iOS中多线程原理与runloop介绍
- 笔记-iOS弹幕(源码)实现原理解析
- iOS App 签名的原理
- iOS 面试题(16):解释垃圾回收的原理
- IOS吹一吹原理的实现