iOS开发底层之类的底层Cache_t 探究 - 07
Posted iOS_developer_zhong
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了iOS开发底层之类的底层Cache_t 探究 - 07相关的知识,希望对你有一定的参考价值。
文章目录
遗漏知识补充
1. LLDB调试,发现 对象的 isa 和类的 isa 不一样, 而类的 isa 与元类的一样, 那是因为对象的 isa 中不仅包含了存储类, 还包含了 其他的值,如 引用计数, 是否正在释放,weak 等。
一. 面试题
1、isKindOfClass 与isMemberOfClass 底层探索
做个测试:
// class_data_bits_t
void lgKindofDemo(void)
BOOL re1 = [(id)[NSObject class] isKindOfClass:[NSObject class]]; //
BOOL re2 = [(id)[NSObject class] isMemberOfClass:[NSObject class]]; //
BOOL re3 = [(id)[LGPerson class] isKindOfClass:[LGPerson class]]; //
BOOL re4 = [(id)[LGPerson class] isMemberOfClass:[LGPerson class]]; //
NSLog(@" \\n re1 :%hhd\\n re2 :%hhd\\n re3 :%hhd\\n re4 :%hhd\\n",re1,re2,re3,re4);
// 打印的结果为: 1 0 0 0
BOOL re5 = [(id)[NSObject alloc] isKindOfClass:[NSObject class]]; //
BOOL re6 = [(id)[NSObject alloc] isMemberOfClass:[NSObject class]]; //
BOOL re7 = [(id)[LGPerson alloc] isKindOfClass:[LGPerson class]]; //
BOOL re8 = [(id)[LGPerson alloc] isMemberOfClass:[LGPerson class]]; //
NSLog(@" \\n re5 :%hhd\\n re6 :%hhd\\n re7 :%hhd\\n re8 :%hhd\\n",re5,re6,re7,re8);
// 打印的结果为: 1 1 1 1
可能会对上面的打印结果懵逼,进入底层看看,就会非常清晰,下面展示下 isMemberOfClass ,isKindOfClass的源码。
📢:打开汇编,查看真正走的isKindOfClass的源码为: objc_opt_isKindOfClass方法
// Calls [obj isKindOfClass]
BOOL
objc_opt_isKindOfClass(id obj, Class otherClass)
#if __OBJC2__ // 只需要看这个
if (slowpath(!obj)) return NO;
Class cls = obj->getIsa();
if (fastpath(!cls->hasCustomCore()))
for (Class tcls = cls; tcls; tcls = tcls->getSuperclass())
if (tcls == otherClass) return YES;
return NO;
#endif
return ((BOOL(*)(id, SEL, Class))objc_msgSend)(obj, @selector(isKindOfClass:), otherClass);
为了加深印象,请看iOS 对实例、类对象、元类、根元类验证
后续需要自己去玩一下!!!
二.Cache_t 底层探索
cache_t 底层结构
cache_t LLDB调试
-
通过lldb调试下底层的 cache_t是怎么存储方法的。 注意看注释
哈希结构,方便存储也方便插入、删除,结合了数组和链表的一些优点。 -
接上面的操作继续, 拿到了 $14对象,(cache_t对象),我们现在去看看它的内部,有什么方法是可以直接打印出方法名(SEL)以及实现(IMP).
//SEL方法
inline SEL sel() const return _sel.load(memory_order_relaxed);
//IMP方法
inline IMP imp(UNUSED_WITHOUT_PTRAUTH bucket_t *base, Class cls) const
uintptr_t imp = _imp.load(memory_order_relaxed);
if (!imp) return nil;
SEL sel = _sel.load(memory_order_relaxed);
return (IMP)
// 在cache_t内部有这两个方法,下面继续用lldb调试
- LLDB的操作,接第1步,继续操作。
(lldb) p $14.sel()
(SEL) $15 = "saySomething"
(lldb) p $14.imp(nil, pClass)
(IMP) $16 = 0x0000000100003c20 (KCObjcBuild`-[LGPerson saySomething])
cache_t 脱离源码调试技巧。
- 把底层的数据结构,复制出来,自定义一个对象与系统一致,然后通过强转层我们自己定义的对象, 然后通过log日志去调试。
==来自大神的代码: ==
typedef uint32_t mask_t; // x86_64 & arm64 asm are less efficient with 16-bits
struct kc_bucket_t
SEL _sel;
IMP _imp;
;
struct kc_cache_t
struct kc_bucket_t *_bukets; // 8
mask_t _maybeMask; // 4
uint16_t _flags; // 2
uint16_t _occupied; // 2
;
struct kc_class_data_bits_t
uintptr_t bits;
;
// cache class
struct kc_objc_class
Class isa;
Class superclass;
struct kc_cache_t cache; // formerly cache pointer and vtable
struct kc_class_data_bits_t bits;
;
int main(int argc, const char * argv[])
@autoreleasepool
Person *p = [Person alloc];
Class pClass = p.class; // objc_clas
[p say1];
[p say2];
[p say3];
[p say4];
[p say1];
[p say2];
// [p say3];
[pClass sayHappy];
struct kc_objc_class *kc_class = (__bridge struct kc_objc_class *)(pClass);
NSLog(@"%hu - %u",kc_class->cache._occupied,kc_class->cache._maybeMask);
// 0 - 8136976 count
// 1 - 3
// 1: 源码无法调试
// 2: LLDB
// 3: 小规模取样
// 底层原理
// a: 1-3 -> 1 - 7
// b: (null) - 0x0 方法去哪???
// c: 2 - 7 + say4 - 0xb850 + 没有类方法
// d: NSObject 父类
for (mask_t i = 0; i<kc_class->cache._maybeMask; i++)
struct kc_bucket_t bucket = kc_class->cache._bukets[i];
NSLog(@"%@ - %pf",NSStringFromSelector(bucket._sel),bucket._imp);
NSLog(@"Hello, World!");
return 0;
cache_t 底层深入分析。
- 找切入点。 通过插入函数,来分析,到底cache 是怎么工作的。
struct cache_t
private:
explicit_atomic<uintptr_t> _bucketsAndMaybeMask; //
union
struct
explicit_atomic<mask_t> _maybeMask; // 4 总得大小
#if __LP64__
uint16_t _flags; // 2
#endif
uint16_t _occupied; // 2 当前占用的方法数
;
explicit_atomic<preopt_cache_t *> _originalPreoptCache; // 8
;
_maybeMask 的含义为总大小个数, _occupied的含义为当前占用的个数。
用上面的脱机代码来跑个测试,看看代码:
LGPerson *p = [LGPerson alloc];
Class pClass = p.class; // objc_clas
[p say1];
[p say2];
[pClass sayHappy];
struct kc_objc_class *kc_class = (__bridge struct kc_objc_class *)(pClass);
NSLog(@"%hu - %u",kc_class->cache._occupied,kc_class->cache._maybeMask);
//打印结果为 2 - 3 ,就是占用2个,总大小为3 。
// 举例2
LGPerson *p = [LGPerson alloc];
Class pClass = p.class; // objc_clas
[p say1];
[p say2];
[p say3];
[p say4];
[p say1];
[p say2];
[p say3];
[pClass sayHappy];
struct kc_objc_class *kc_class = (__bridge struct kc_objc_class *)(pClass);
NSLog(@"%hu - %u",kc_class->cache._occupied,kc_class->cache._maybeMask);
// 打印结果为 4 - 7,就是占用4个大小,总大小为7.
- 通过上面的代码,就很奇怪,为什么总大小会自动变,系统是用了什么策略进行扩容的。 接下来来探索下 objc源码。
下面是cache_t的内部结构源码,找到 insert犯法
- 进入方法内部
void cache_t::insert(SEL sel, IMP imp, id receiver)
//省略部分代码
if (slowpath(isConstantEmptyCache())) //1. 第一次进入
// Cache is read-only. Replace it.
if (!capacity) capacity = INIT_CACHE_SIZE;//4个空间
reallocate(oldCapacity, capacity, /* 开辟4个大小的空间 */false);
else if (fastpath(newOccupied + CACHE_END_MARKER <= cache_fill_ratio(capacity))) // 2. 如果空间没有满,就正常处理
// Cache is less than 3/4 or 7/8 full. Use it as-is.
#if CACHE_ALLOW_FULL_UTILIZATION
else if (capacity <= FULL_UTILIZATION_CACHE_SIZE && newOccupied + CACHE_END_MARKER <= capacity) //3. 允许占用100%缓存,并且没有超出分配的大小,就正常使用
// Allow 100% cache utilization for small buckets. Use it as-is.
#endif
else // 4. 其他的情况就是超出了分配的大小,就进行2倍扩容, 以前是4, 现在扩容后就变成了8位。
capacity = capacity ? capacity * 2 : INIT_CACHE_SIZE;
if (capacity > MAX_CACHE_SIZE)
capacity = MAX_CACHE_SIZE;
reallocate(oldCapacity, capacity, true);
疑问1: 扩容是8个,为啥上面调试打印为7呢,继续走入 reallocate方法。
void cache_t::reallocate(mask_t oldCapacity, mask_t newCapacity, bool freeOld)
bucket_t *oldBuckets = buckets();
bucket_t *newBuckets = allocateBuckets(newCapacity);
// 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);
//1. 原来是这个地方,会减去一个1,难怪外部调试的时候,扩容8,显示7的原因
setBucketsAndMask(newBuckets, newCapacity - 1);
if (freeOld)
collect_free(oldBuckets, oldCapacity);
遗漏
Cache_t 原理图后续补上,几天没睡好了,去补一觉
以上是关于iOS开发底层之类的底层Cache_t 探究 - 07的主要内容,如果未能解决你的问题,请参考以下文章
iOS开发底层之RuntimeObjc_msgSend探究 - 08