OC底层学习-类的底层结构探究下篇

前言

上一章类的底层结构探究上篇中我们知道了

  • isa的指向图,并且引入了元类,其中isa会指向元类
  • 并且还知道了继承链
  • 继而我们还探究了的底层结构,并且针对与里面的bits变量做了主要探究(这边大家在探究的时候注意一下要在objc源码中探究),知道了bits里面存储了属性方法协议等;
  • 进一步还得到成员变量没有存在class_rw_tpropertys中,而是在class_ro_tivar_t中;
  • 对象方法存在类class_rw_tmethods中,类方法存在元类class_rw_tmethods中.

看类的底层结构我们会经常性的看到class_rw_tclass_ro_t,那这两兄弟是什么呢?我们知道系统也会为属性生成_属性名的成员变量存在class_ro_tivar_t中,那属性成员变量实例变量之间又有个什么关系呢?那我们就一起来看看类的底层结构下篇

Class in Memory

在探究之前我们先来看看Apple在WWDC 2020讲解runtime时内存优化的视频片段;

通过视频我们可以了解到本次主要是对内部数据结构做了优化,使得App运行更快,这其中就有两个概念Clean MemoryDirty Memory,那这两者究竟是什么呢?继续看视频可以得出:

Clean Memory

  • 加载后不会发生更改的内存;
  • class_ro_t 就是属于 Clean Memory,因为class_ro_t是只读的;
  • Clean Memory可以进行移除从而达到节省更多的内存空间,因为如果你需要Clean Memory,系统可以从磁盘中重新加载

Dirty Memory

  • 在进程运行时会发生更改的内存;
  • 类结构一经使用就会变成Dirty Memory,因为运行时会向它写入新的数据;例如创建一个新的方法缓存并从类中指向它;
  • Dirty Memory要比Clean Memory昂贵的多,只要进程运行,它就必须一直存在。

macOS中可以通过swap选择换出Dirty Memory,而在iOS中不使用swap,所以Dirty MemoryiOS中代价很大,所以Dirty Memory是这个类数据被分成两部分的原因,保持清洁的数据越多越好,通过分离那些永远不会更改的数据,所以可以把大部分类数据存储在Clean Memory中。

我们知道类在磁盘中

未命名文件(11).png

在运行时需要知道更多类的信息,所以当类第一次加载到内存中时,runtime会为它分配额外的存储容量,额外分配的存储容量是class_rw_t读取-编写数据的;在这个数据结构中我们会存储只有在运行时生成的新信息,例如所有类都会链接成一个树状结构,这就是通过First SubClassNext Sibling Class指针实现的,这样就允许runtime遍历当前所有用到的类,使得方法缓存无效非常有用。

根据上面的图解,我们肯定会有疑问啊,既然为了节省内存空间而优化,那为什么方法,属性在class_ro_t中时,class_rw_t中也存在呢?视频中也给我讲解了:

  • 它们在runtime的时候可以被修改;
    • Category被加载时,它可以给类添加方法;
    • 通过runtime api动态添加属性和方法;
  • class_ro_t是只读的,需要在class_rw_t追踪这些数据。

我们知道项目中会有很多的类,那么这么做的话将会占用相当大的内存空间,那如何缩小这些结构呢?

class_rw_t优化

runtime可以动态添加属性和方法,但是Apple在实际监测中发现大约只有10%的类动态修改了,这其中的一个字段demangledName只有在swift中有需要访问其objective-c名称时才需要;
所以就可以拆分掉那些平时不用的部分:

未命名文件(12).png

这样的话,class_rw_t的大小减少了一半;对那些需要修改内存的,需要额外信息的类,我们可以分配这些扩展记录中的一个,并把它滑到类中供其使用:

未命名文件(13).png

class_rw_t的优化总结

class_rw_t的优化实质上就是将其内部不常用的数据拆分出来放在Class_rw_ext_t扩展中;如果需要使用这部分数据就从Class_rw_ext_t扩展中分配一个滑到类中供其使用。

类中的变量,属性,方法

我们知道一个类中一般都会有变量,属性和方法。下面就是我们声明的一个继承自NSObject的类LhkhPerson

image.png

  • nickNameobjc均为成员变量,但是我们这边需要注意一点,其实objc准确的讲我们应该叫它为实例变量(定义为除基础数据类型以外的成员变量,所以实例变量是一种特殊的成员变量),因为NSObject不是基础类型;总结来说就是基础数据类型的都是成员变量,除此之外就是实例变量;
  • name1name2以及age就是属性;
  • saySomething为对象方法,sayHello为类方法

我们上一节补充中知道了成员变量存储在class_ro_tivar_t中,

image.png
我们发现它的成员变量显示有5个,但是我们只声明2个啊,还有3个是什么?

我们可以通过取ivars里面的数据可以得出

image.png
这三个与我们定义的属性有点像啊,怎么会有个‘_’呢?接下来我们通过clang编译成.cpp文件来看一下下层代码实现:

image.png

image.png

编译时系统将属性转换成为了_属性的成员变量和对应的getter和setter方法。

所以我们得出:

  • 属性 = _属性的成员变量 + getter方法 + setter方法

细心的我们肯定会发现同样都是NSStringname1name2setter方法是不一样的额

image.png
在上图中我们可以知道,name2是通过首地址加上偏移量赋值的,而name1是通过一个objc_setProperty方法,相同的类型为什么会出现不同的set方式呢?就是因为我们给的修饰词不同出现的吗?

objc_setProperty补充

我们需要先了解一下objc_setProperty这个是什么意思?

LLVM源码中的objc_setProperty

由于这是在编译时就出现了,那我们就得从LLVM下手了,使用vscode打开LLVM源码:

image.png

通过搜索objc_setProperty我们发现了这个方法,在运行时创建objc_setProperty方法,既然找到了创建这个方法,那么我们就逆着找呗,什么情况下才会调用getSetPropertyFn()这个方法呢,继续搜索:

image.png
GetPropertySetFunction()方法中会调用,这个是一个中间方法,那继续找GetPropertySetFunction()调用:

image.png

我们发现这边是一个Switch,而会调用GetPropertySetFunction()是在Switch的PropertyImplStrategy策略下,而这个会对应两个GetSetProperty``和SetPropertyAndExpressionGet,那我们现在就只需要知道什么时候赋值这个策略不就ok了吗,继续找:

image.png
是否是copy修饰词;

image.png
还有retain(MRC模式下,现在基本都是在ARC模式下,所有retain基本就可以不需要理解),atomic等修饰词。

那我们来个实例验证一下:

image.png

image.png

总结

所以我们基本可以得到的是在copy修饰的情况下,不管是原子性还是非原子性,系统生成的setter方法都会重定向到objc_setProperty方法。

objc源码中的objc_setProperty

看到这个方法前面带着objc我们想着objc源码中有没有方法实现呢,经过搜索我们发现

image.png

image.png
从源码中我们发现有5个方法,我们同样发现atomiccopynonatomic;而且5个方法中都会调用reallySetProperty方法,而在这个方法中实质原理就是新值retain,旧值release

编码

我们在生成的cpp文件中会看到

image.png
这个中"v16@0:8""@16@0:8"这些个编码是啥意思呢。。。请看官方编码解释:

官方编码

我们可以通过打开xcode–> command+shift+0–> 搜索ivar_getTypeEncoding–> 点击Type Encodings

传送门

Code Meaning
c char
i An int
s short
l long``l is treated as a 32-bit quantity on 64-bit programs.
q long long
C An unsigned char
I An unsigned int
S An unsigned short
L An unsigned long
Q An unsigned long long
f float
d double
B A C++ bool or a C99 _Bool
v void
* A character string (char *)
@ An object (whether statically typed or typed id)
# A class object (Class)
: A method selector (SEL)
[array type] An array
{name=type… } A structure
(name=type… ) A union
bnum A bit field of num bits
^type A pointer to type
? An unknown type (among other things, this code is used for function pointers)

也可以通过代码打印

image.png

Objective-C 不支持long double类型,@encode(long double)返回d,和double类型的编码值一样。

我们这边以name1a这个属性的编码为例 "@16@0:8"解释一下:

  • @: 对应id类型参数 self
  • 16:上面参数从16位置开始
  • @: 参数
  • 0:参数从0位置开始
  • ::SEL
  • 8:SEL8位置开始
© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享