热门标签 | 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;去补一觉






推荐阅读
  • 本文将介绍如何编写一些有趣的VBScript脚本,这些脚本可以在朋友之间进行无害的恶作剧。通过简单的代码示例,帮助您了解VBScript的基本语法和功能。 ... [详细]
  • Explore how Matterverse is redefining the metaverse experience, creating immersive and meaningful virtual environments that foster genuine connections and economic opportunities. ... [详细]
  • 1:有如下一段程序:packagea.b.c;publicclassTest{privatestaticinti0;publicintgetNext(){return ... [详细]
  • 深入理解 SQL 视图、存储过程与事务
    本文详细介绍了SQL中的视图、存储过程和事务的概念及应用。视图为用户提供了一种灵活的数据查询方式,存储过程则封装了复杂的SQL逻辑,而事务确保了数据库操作的完整性和一致性。 ... [详细]
  • c# – UWP:BrightnessOverride StartOverride逻辑 ... [详细]
  • 本文详细介绍了Java中org.w3c.dom.Text类的splitText()方法,通过多个代码示例展示了其实际应用。该方法用于将文本节点在指定位置拆分为两个节点,并保持在文档树中。 ... [详细]
  • Python 异步编程:深入理解 asyncio 库(上)
    本文介绍了 Python 3.4 版本引入的标准库 asyncio,该库为异步 IO 提供了强大的支持。我们将探讨为什么需要 asyncio,以及它如何简化并发编程的复杂性,并详细介绍其核心概念和使用方法。 ... [详细]
  • 技术分享:从动态网站提取站点密钥的解决方案
    本文探讨了如何从动态网站中提取站点密钥,特别是针对验证码(reCAPTCHA)的处理方法。通过结合Selenium和requests库,提供了详细的代码示例和优化建议。 ... [详细]
  • 本文详细介绍了如何在Linux系统上安装和配置Smokeping,以实现对网络链路质量的实时监控。通过详细的步骤和必要的依赖包安装,确保用户能够顺利完成部署并优化其网络性能监控。 ... [详细]
  • C++实现经典排序算法
    本文详细介绍了七种经典的排序算法及其性能分析。每种算法的平均、最坏和最好情况的时间复杂度、辅助空间需求以及稳定性都被列出,帮助读者全面了解这些排序方法的特点。 ... [详细]
  • 本文详细探讨了Java中的24种设计模式及其应用,并介绍了七大面向对象设计原则。通过创建型、结构型和行为型模式的分类,帮助开发者更好地理解和应用这些模式,提升代码质量和可维护性。 ... [详细]
  • CentOS7源码编译安装MySQL5.6
    2019独角兽企业重金招聘Python工程师标准一、先在cmake官网下个最新的cmake源码包cmake官网:https:www.cmake.org如此时最新 ... [详细]
  • 数据库内核开发入门 | 搭建研发环境的初步指南
    本课程将带你从零开始,逐步掌握数据库内核开发的基础知识和实践技能,重点介绍如何搭建OceanBase的开发环境。 ... [详细]
  • ImmutableX Poised to Pioneer Web3 Gaming Revolution
    ImmutableX is set to spearhead the evolution of Web3 gaming, with its innovative technologies and strategic partnerships driving significant advancements in the industry. ... [详细]
  • 本文详细介绍了macOS系统的核心组件,包括如何管理其安全特性——系统完整性保护(SIP),并探讨了不同版本的更新亮点。对于使用macOS系统的用户来说,了解这些信息有助于更好地管理和优化系统性能。 ... [详细]
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社区 版权所有