【iOS】NSString的三种实现方式

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档


前言

最近被学长问到了有关NSString底层的三种实现方式,但是回答不上来,特此撰写博客记录


实现方式

在 OC中,NSString类型有三种实现方式,分别是

__NSCFConstantString
__NSCFString
NSTaggedPointerString

知识引入

在这里插入图片描述

编译结果:
在这里插入图片描述

这里有一点需要注意的就是我们在使用引用计数时,我们必须要先关闭我们的ARC
在这里插入图片描述

##理解实现方式

可以看到我们用不同方式创建不同长度的字符串出现了不同的类型,接下来我们一一对其进行解释

扫描二维码关注公众号,回复: 15853531 查看本文章

__NSCFConstantString

我们可以对这个类型的通俗理解就是常量字符串。
在OC中,字符串字面量(例如@“Hello”)被视为常量,并存储在常量数据区域。它被看作是一种编译时常量。它用于表示字符串字面量,这些字符串在编译时就被确定,并在运行时作为常量存储在常量数据区域。
编译器会将字符串字面量优化为常量,在编译时就确定其内容,并将其存储在可执行文件的常量数据区域。
这样做的好处是可以提高字符串的访问效率,并且节省内存空间,因为每个相同的字符串字面量只需要存储一份,这就对应着我们str1与str1_的地址是相等的。

同时我们通过相同方式创建的两个对象如果地址相等,那么也证明他是一种单例

另外我们可以看到它的引用计数十分庞大,说明这种类型的实例并不能被释放

所以在这里我们得出结论,常量字符串是一种单例
在这里插入图片描述

__NSCFConstantString 对象的特点是不可变的,即无法修改其内容。由于字符串字面量是不可变的,因此它们在创建后不能被修改。__NSCFConstantString
类型的对象被设计为只读,以确保字符串字面量的不可变性。

我们一般通过@“…”、CFSTR(“…”) 或者 stringWithString: 方法来生成这种类型

这种对象存储在字符串常量区

__NSCFString

和 __NSCFConstantString 不同, __NSCFString 对象是在运行时创建的一种 NSString 子类,他并不是一种字符串常量。所以和其他的对象一样在被创建时获得了 1 的引用计数。

通过 NSString 的 stringWithFormat 等方法创建的 NSString 对象一般都是这种类型。

这种对象被存储在堆上。

在我们的代码中可以看到:
在这里插入图片描述

我们将相同类型的字符串通过stringWithFormat方法赋给不同的实例时,实例的地址不同,也间接说明了存储在堆上的对象,即使两个对象的内容相同,它们在堆上的内存地址也是不同的。每个对象都在独立的内存空间中存储,具有自己的地址。这意味着通过不同的对象引用访问这两个对象时,实际上访问的是不同的内存地址。

NSTaggedPointerString

NSTaggedPointerString 是一种特殊类型的字符串,在 Objective-C 中用于表示较短的字符串对象。

TaggedPointer的意思是标签指针,这是苹果在 64 位环境下对 NSString,NSNumber
等对象做的一些优化。简单来讲可以理解为把指针指向的内容直接放在了指针变量的内存地址中,因为在 64 位环境下指针变量的大小达到了 8
位足以容纳一些长度较小的内容。于是使用了标签指针这种方式来优化数据的存储方式。从他的引用计数可以看出,这货也是一个释放不掉的单例常量对象。在运行时根据实际情况创建。

对于 NSString 对象来讲,当非字面值常量的数字,英文字母字符串的长度小于等于 9 的时候会自动成为 NSTaggedPointerString 类型,如果有中文或其他特殊符号(可能是非 ASCII 字符)存在的话则会直接成为 )__NSCFString 类型。

这一点在代码实例中也有体现

这种对象被直接存储在指针的内容中,可以当作一种伪对象。

三种类型的深浅复制

我们先回顾一下我们深浅复制的定义

浅复制是指创建一个新对象,该对象与原始对象共享内部数据的引用。换句话说,浅复制只复制对象本身,而不复制对象所引用的数据。因此,原始对象和副本对象指向同一块内存,对其中一个对象的修改会影响另一个对象。
通俗的讲,浅复制就是仅仅创建一个存放与复制对象相同地址的指针变量,也可以称为指针拷贝

深复制是指创建一个新对象,并复制原始对象及其引用的所有数据。深复制会递归地复制对象的所有内容,包括对象所引用的其他对象。这样,原始对象和副本对象拥有独立的内存空间,彼此之间的修改互不影响。
通俗的讲,深复制就是创建一个与被复制的对象的值完全相同的对象,但是他们的地址是不同的,因此深复制也可以称为内容拷贝

浅拷贝就是拷贝指向原来对象的指针,使原对象的引用计数+1,可以理解为创建了一个指向原对象的新指针而已,并没有创建一个全新的对象。而深复制则不会是引用计数+1.

在这里插入图片描述

编译结果:
在这里插入图片描述

通过编译结果可以看到,我们的NSString的三种实现方式的深浅复制与我们容器类对象的深浅复制完全相同。至于容器类的深浅复制的原理,笔者会另外撰写一篇博客来专门讲述。

这里我们还可以明白copy被称为指针拷贝的原因,当我们对我们的__NSCFString类的对象进行浅拷贝时,可以看到它的引用计数加了1。同时因为其他两种类时单例,所以无论怎么进行操作其引用计数都不会增加

这里还有一点需要注意的,就是我们对NSString类进行复制时,复制后的对象全部都属于__NSCFConstantString。

这一点笔者通过使用ChatGPT解决了疑问,但并不能保证其正确性,在后续学习过程中如果有误会返回进行修改
在这里插入图片描述

总结

本文讲述了NSString的三种实现方式,接下来总结一下学到的知识

1、__NSCFConstantString:

我们一般通过@“…”、CFSTR(“…”) 或者 stringWithString: 方法来生成这种类型

这种对象存储在常量区,是一种编译时常量,同时还是一种单例

好处是可以提高字符串的访问效率,并且节省内存空间

2、__NSCFString

通过 NSString 的 stringWithFormat 等方法创建的 NSString 对象一般都是这种类型。

他并不是一种字符串常量。所以和其他的对象一样在被创建时获得了 1 的引用计数。

这种对象被存储在堆上。

3、NSTaggedPointerString

通过 NSString 的 stringWithFormat: 等方法创建的 NSString 对象有可能是这种类型,取决于字符串的长度。

小于等于9的数字或英文字母字符可能会成为这种类型,对于中文与其他特殊字符就直接成为__NSCFString类型

也是一个释放不掉的单例常量对象。在运行时根据实际情况创建。

这种对象被直接存储在指针的内容中

4、三种类型的深浅复制

符合容器类对象的深浅复制原理

小问题:

1、为什么用@与stringWithString方法创建的对象是同一个对象:

stringWithString: 方法用于创建一个新的字符串对象,该对象是传入的字符串对象的副本。而字符串字面量(例如
@“Hello”)已经是一个不可变的 NSString 对象,不需要复制副本,因为它们本身就是不可变的。

2、我们为什么无法直接创建这些实例

在 Objective-C 中,由于 __NSCFConstantString、__NSCFString 和
NSTaggedPointerString 是运行时库的私有类,因此无法直接创建它们的实例。相反,我们可以通过使用 NSString 或
NSMutableString 类型的对象来间接地创建这些实现方式。

3、运行库的私有类是什么意思

在 Objective-C 中,运行时库的私有类是指在编译器和官方文档中未公开或未公开文档化的类。这些类是运行时库的内部实现细节,用于支持
Objective-C 的各种功能和特性。它们通常不直接暴露给开发者使用,而是由公共类进行封装和提供更高级的接口。

私有类在运行时库中扮演着重要的角色,用于实现字符串、数组、集合、内存管理、消息传递等核心功能。这些类可能包含更底层的数据结构和算法,用于提高性能、优化内存占用和实现特定的语言特性。

猜你喜欢

转载自blog.csdn.net/weixin_72437555/article/details/130769750