「这是我参与2022首次更文挑战的第26天,活动详情查看:2022首次更文挑战」。
- 本文主要探究Swift中
String的源码探究。
1.String在内存中是如何存储的
在OC中我们知道字符串NSString是一个OC对象,对于较少的长度的时候它是一个TargetPoint直接存储在指针中,对于长创建对象存储在堆区。那么Swift中呢?
我们一起来研究一下 String 这个类,我们先来看一下当我们创建一个空的字符串发生了什么?
只有一个这个值,和我们之前学习class对象的内存结构不一样。因此我们可以去是找到 String 的源码,然后找到对应的初始化方法,这里我们直接搜索源文通过快捷键Command+p 件就可以看到这样的代码:
我们查找empty,可以看到对于空字符串创建的方法。
String是一个值类型,它的结构体中存在_StringGuts类型成员,上面创建空字符串也是关于它。我们去查找这个_StringGuts文件
可以发现关于Empty string的定义,里面引入了_StringObject对象。我们可以看到上面对于
_StringGuts 结构中存在一个_StringObject的成员变量。
因此我们对于String实际上存储的时_StringObject,我们查看_StringObject,后查看empty的实现方法。
里面包含不同架构的判断,我们看init(count 方法的实现。最终看到它实现
对它属性的赋值,因此对于String字符串来说,我们实际上是对它结构体的成员变量进行赋值。
Variant上面可以知道count就是字符串的大小,那么Variant是什么呢?
其中immortal表示的是一个原生字符串,一种是native,最后bridged(_CocoaString)表示的NSString。
空字符串则表示传入的0,此时它在内存中存的就是0.
- Nibbles
我们看空字符串定义方法中 discriminator: Nibbles.emptyString,我们看下Nibbles是什么
里面没有定义枚举类型,我们看下它的extension
一些定义
emptyString 返回的是一个
Nibbles 的 samll(isASCII:) 方法,true 代表是 ASCII,false 代表不是。我们来看这个 samll(isASCII:) 的实现。
可以看到,当
isASCII 为 true 的时候,返回是 0xE000_0000_0000_0000,否则返回 0xA000_0000_0000_0000。
我们传入的是中文的话,表示不是
ASCII,因此是0xa开头。
2 小字符串内存布局
我们看下小字符串
我们定义的符合ASCII,因此0xe开头,2表示的字符串的长度。
我们自己添加一个字符,变成了3,前面的则表示abc的ASCII对应的值。对于小字符串来说,值直接存在了指针了。
对于8个长度的字符串前面填满了,那么对于超过8呢
我们取15个字符,刚好存在2个指针中,那么长度大于15呢
3. 大字符串内存布局
可以发现和我们之前小字符串存储的方式不再一样了。
对于原生的Swift字符串来说,采取的是 tail-allocated 存储,也就是在当前实例分配有超出 其最后存储属性的额外空间,额外的空间可用于直接在实例中存储任意数据,无需额外的堆分配
这里可以看到nativeBias 的值为 32。其中 discriminator(鉴别器) 和 objectAddr,根据官方给的注释,这个 discriminator 在 64 位中,占据的位置是高 63 位到高 60 位。那高 60 位到低 0 位存储的就是这个额外的存储空间的内存地址。
这个 objectAddr 存储的是这个额外的存储空间的内存地址,但是它是一个相对地址,因为它需要加上 nativeBias,得到的才是这个额外的存储空间的地址值。
0x8 表示的是一个大的原生字符串
后面的100003f00表示的是相对地址信息,我们转换为10进制+32
转换16进制得到
打印内存地址情况:
可以发现存储的就是我们字符串的值。
接下来我们再来看一下前面的 8 个字节(0xd000000000000010)是什么东西。在官方的注释里有一个大字符串关于标志位相关的描述,如图:
-
isASCII:用来判断当前字符串是否是 ASCII,在高 63 位。
-
isNFC:这个默认为 1,在高 62 位。
-
isNativelyStored:是否是原生存储,在 高 61 位。
-
isTailAllocated:是否是尾部分配的,在 高 60 位。
-
TBD:留作将来使用,在高 59 位到高 48 位。
-
count:当前字符串的大小,在高 47位到低 0 位。
我们把前面的抹零
可以看到这个和官方注释的描述是一致的,那么 0x10 的十六进制是16,所以当前字符串的大小为 16。所以当字符串为大字符串的时候,前 8 个字节存储的就是 countsAndFlags。