iOS 底层原理探索 之 isa - 类的底层原理结构(下)

1,290 阅读5分钟
写在前面: iOS底层原理探究是本人在平时的开发和学习中不断积累的一段进阶之
路的。 记录我的不断探索之旅,希望能有帮助到各位读者朋友。

目录如下:

  1. iOS 底层原理探索 之 alloc
  2. iOS 底层原理探索 之 结构体内存对齐
  3. iOS 底层原理探索 之 对象的本质 & isa的底层实现
  4. iOS 底层原理探索 之 isa - 类的底层原理结构(上)
  5. iOS 底层原理探索 之 isa - 类的底层原理结构(中)

以上内容的总结专栏


细枝末节整理


写在前面

在之前的系列文章中,我们从开发中使用最多和最基础的 alloc 开始,探索了其底层的流程,以及类底层结构体的内存知识,接着关于对象的本质和其内部isa的底层实现,进一步的,我们探索了类的bitscache

那么,本片作为类的底层原理结构的收官内容,我会从整体来总结下。

准备

来自苹果WWDC2020关于 Objective-C 运行时做出的更改

Objective-C运行时的进步

深入到每个Objective-C和Swift类背后的低级位元和字节的微观世界。
了解最近对内部数据结构、方法列表和标记指针的更改如何提供更好的性能
和更低的内存使用。我们将演示如何识别和修复取决于内部细节的代码崩溃,
并向您展示如何保持代码不受运行时更改的影响。
  • clean memory 是指加载后不会发生改变的内存。(class_ro_t 就属于 clean memory 因为他是只读的)
  • dirty memory 是指在进程运行时会发生更改的内存。(类结构一经使用就会变成 dirty memory,因为运行时会向他写入新的数据,例如 创建一个新的方法缓存并从类中指向它)
  • dirty memoryclean memory昂贵得多。它必须在进程运行期间一直存在。另一方面,可以排除clean memory,为其他东西腾出空间,因为如果您需要它,系统总是可以从磁盘重新加载它。macOS有交换dirty memory的选项,但dirty memory在iOS中特别昂贵,因为它不使用交换。

磁盘上app二进制文件中的类

它包含最频繁访问的信息:指向元类、超类和方法缓存的指针。 image.png

当类第一次从磁盘加在到内存时的结构

它还有一个指针,指向存储其他信息的更多数据,称为class_ro_t Ro代表只读。这包括类的名称和关于方法、协议和实例变量的信息。Swift类和Objective-C类共享这个基础设施,所以每个Swift类也有这些数据结构。

image.png

当类第一次被使用时

当一个类第一次被使用时,运行时将为它分配额外的存储空间。 这个运行时分配的存储是class_rw_t,用于读/写数据。 在这个数据结构中,我们存储只在运行时生成的新信息。当一个类别被加载时,它可以向类中添加新方法,程序员可以使用运行时api动态地添加它们。 因为class_ro_t是只读的,所以我们需要跟踪class_rw_t中的这些内容。

image.png

类的整体结构

将通常不使用的部件分开,这样 class_rw_t 的大小减少了一半 image.png

cache_tinsert() 调用的时机

通过源码断点调试,我们在堆栈信息中可以找到

image.png

log_and_fill_cache

lookUpImpOrForward

这就是,之后我们要去探索的关于消息发送转发和查找的相关流程内容。留着我们之后的文章展开探索。

补充

  • 面试题

截屏2021-07-22 18.52.28.png

源码分析

我们可以从源码中找到方法的实现: image.png 分析可知:

  • 类的 isKindOfClass 方法 是 先根据 isa 找到元类, 做比较, 然后找到元类的父类 ,再做比较,循环下去, 只要跟着 isa 可以找到元类就比较,只要有一次可以比对上,就返回 YES。

  • 类的 isMemberOfClass 方法 是 看自己的元类 和 要比较的类是否相等;

  • 实例对象的 isKindOfClass 方法 是 看自己的类是否和方法传进来的参数相同;

  • 实例对象的 isMemberOfClass 方法 是 看自己的类 和 要比较的类是否相等;

  1. 对于 t1 而言, NSObject classisaNSObject MateClass, NSObject MateClasssuperClassNSObject class 所以, 返回 YES;
  2. 对于 t2 而言, SMPerson classisaSMPerson Mateclass,不等于 SMPerson classSMPerson MateclasssuperclassNSObject MateClass,不等于 SMPerson class , 所以返回 NO;
  3. 对于 t3 而言, NSObject classisaNSObject MateClass, 不等于 NSObject class, 所以返回 NO;
  4. 对于 t4 而言, SMPerson classisaSMPerson MateClass, 不等于 SMPerson class ,所以返回NO。
  5. 对于 t5 而言, objcisaNSObject class ,等于 NSObject class, 所以是 YES;
  6. 对于 t6 而言 objcisaSMPerson class, 所以返回 YES;
  7. 对于 t7 而言, objcisaNSObjcet class, 所以返回 YES;
  8. 对于 t8 而言, objcisaSMPersn class, 所以返回YES。

输出如下, 与我们根据源码分析的一样。

 1 - 0 - 0 - 0 - 1 - 1 - 1 - 1

LLDB调试

是否真的如我们分析的过程一致呢?接下来我们开始LLDB调试验证过程:

如上图所示,你也看到了,我要通过断点代码调试来展开分析一下内容: 熟悉的 debug workdfllow -> always show disassembly

image.png

打断点发现 isMemberOfClass 方法走了断点, isKindOfClass 没有走断点。

发现系统会先调用 objc_opt_class 的方法来, 之后 去到 objc_opt_isKindOfClass方法, 那么,打开我们的源码工程查找看看:

image.png

objc_opt_class 方法 获取 传入参数的 isa 指向的类, 如果 isa 指向的类是元类,则返回这个对象,否则返回isa指向的类; 也就是说,实例 调用此方法,返回 实例的类对象; 类 调用此方法 返回它自己;

接着再看;

image.png

方法会拿到 objisa 指向的类 cls , 如果 cls 存在,判断是否等于 otherClass , 等于 则 返回YES, 否则,去找到 cls 的父类, 接着判断是否等于 otherClass, 如此 做一个 for循环,直到最后没有匹配上之后,返回NO。