热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

iOS开发深度解析:探究底层缓存机制Cache_t的实现细节与优化策略

篇首语:本文由编程笔记#小编为大家整理,主要介绍了iOS开发底层之类的底层Cache_t 探究 - 07相关的知识,希望对你有一定的参考价值。

篇首语:本文由编程笔记#小编为大家整理,主要介绍了iOS开发底层之类的底层Cache_t 探究 - 07相关的知识,希望对你有一定的参考价值。








文章目录


  • 遗漏知识补充
  • 一. 面试题
    • 1、isKindOfClass 与isMemberOfClass 底层探索

  • 二.Cache_t 底层探索
    • cache_t 底层结构
    • cache_t LLDB调试
    • cache_t 脱离源码调试技巧。
    • cache_t 底层深入分析。

  • 遗漏





遗漏知识补充

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调试


  1. 通过lldb调试下底层的 cache_t是怎么存储方法的。 注意看注释
    在这里插入图片描述
    哈希结构,方便存储也方便插入、删除,结合了数组和链表的一些优点。

  2. 接上面的操作继续, 拿到了 $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调试

  1. LLDB的操作,接第1步,继续操作。

(lldb) p $14.sel()
(SEL) $15 = "saySomething"
(lldb) p $14.imp(nil, pClass)
(IMP) $16 = 0x0000000100003c20 (KCObjcBuild`-[LGPerson saySomething])

cache_t 脱离源码调试技巧。


  1. 把底层的数据结构,复制出来,自定义一个对象与系统一致,然后通过强转层我们自己定义的对象, 然后通过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 &#61; 0; i<kc_class->cache._maybeMask; i&#43;&#43;) {
struct kc_bucket_t bucket &#61; kc_class->cache._bukets[i];
NSLog(&#64;"%&#64; - %pf",NSStringFromSelector(bucket._sel),bucket._imp);
}
NSLog(&#64;"Hello, World!");
}
return 0;
}

cache_t 底层深入分析。


  1. 找切入点。 通过插入函数&#xff0c;来分析&#xff0c;到底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 的含义为总大小个数&#xff0c; _occupied的含义为当前占用的个数。

用上面的脱机代码来跑个测试&#xff0c;看看代码&#xff1a;

LGPerson *p &#61; [LGPerson alloc];
Class pClass &#61; p.class; // objc_clas
[p say1];
[p say2];

[pClass sayHappy];
struct kc_objc_class *kc_class &#61; (__bridge struct kc_objc_class *)(pClass);
NSLog(&#64;"%hu - %u",kc_class->cache._occupied,kc_class->cache._maybeMask);
//打印结果为 2 - 3 &#xff0c;就是占用2个&#xff0c;总大小为3 。
// 举例2
LGPerson *p &#61; [LGPerson alloc];
Class pClass &#61; 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 &#61; (__bridge struct kc_objc_class *)(pClass);
NSLog(&#64;"%hu - %u",kc_class->cache._occupied,kc_class->cache._maybeMask);
// 打印结果为 4 - 7&#xff0c;就是占用4个大小&#xff0c;总大小为7.


  1. 通过上面的代码&#xff0c;就很奇怪&#xff0c;为什么总大小会自动变&#xff0c;系统是用了什么策略进行扩容的。 接下来来探索下 objc源码。
    下面是cache_t的内部结构源码&#xff0c;找到 insert犯法
    在这里插入图片描述
  2. 进入方法内部

void cache_t::insert(SEL sel, IMP imp, id receiver)
{
//省略部分代码
if (slowpath(isConstantEmptyCache())) { //1. 第一次进入
// Cache is read-only. Replace it.
if (!capacity) capacity &#61; INIT_CACHE_SIZE;//4个空间
reallocate(oldCapacity, capacity, /* 开辟4个大小的空间 */false);
}
else if (fastpath(newOccupied &#43; CACHE_END_MARKER <&#61; cache_fill_ratio(capacity))) { // 2. 如果空间没有满&#xff0c;就正常处理
// Cache is less than 3/4 or 7/8 full. Use it as-is.
}
#if CACHE_ALLOW_FULL_UTILIZATION
else if (capacity <&#61; FULL_UTILIZATION_CACHE_SIZE && newOccupied &#43; CACHE_END_MARKER <&#61; capacity) { //3. 允许占用100%缓存&#xff0c;并且没有超出分配的大小&#xff0c;就正常使用
// Allow 100% cache utilization for small buckets. Use it as-is.
}
#endif
else {// 4. 其他的情况就是超出了分配的大小&#xff0c;就进行2倍扩容&#xff0c; 以前是4&#xff0c; 现在扩容后就变成了8位。
capacity &#61; capacity ? capacity * 2 : INIT_CACHE_SIZE;
if (capacity > MAX_CACHE_SIZE) {
capacity &#61; MAX_CACHE_SIZE;
}
reallocate(oldCapacity, capacity, true);
}
}

疑问1&#xff1a; 扩容是8个&#xff0c;为啥上面调试打印为7呢&#xff0c;继续走入 reallocate方法。

void cache_t::reallocate(mask_t oldCapacity, mask_t newCapacity, bool freeOld)
{
bucket_t *oldBuckets &#61; buckets();
bucket_t *newBuckets &#61; allocateBuckets(newCapacity);
// Cache&#39;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) &#61;&#61; newCapacity-1);
//1. 原来是这个地方&#xff0c;会减去一个1&#xff0c;难怪外部调试的时候&#xff0c;扩容8&#xff0c;显示7的原因
setBucketsAndMask(newBuckets, newCapacity - 1);

if (freeOld) {
collect_free(oldBuckets, oldCapacity);
}
}



遗漏

Cache_t 原理图后续补上&#xff0c;几天没睡好了&#xff0c;去补一觉






推荐阅读
author-avatar
跳海FBA_802
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有