容器类行NSArray等
操作对象为不可变数组
由于对于NSString苹果做了一些特殊的优化,我们先从未被优化的容器类NSArray(或NSDictionary、NSSet等)入手,看如下代码:
1 | { |
1 | 对应输出为: |
对于非mutable类型的NSArray进行copy操作只是对引用计数进行+1操作,类似于retain,也就是所谓的浅拷贝,返回的类型是非mutable类型的NSArray;进行mutableCopy操作才会重新申请内存创建对象,也就是所谓的深拷贝,返回的类型是mutable类型的NSArray;
操作对象为可变数组
1 | { |
1 | 对应输出为: |
对于mutable类型的NSArray进行copy操作时,会重新申请内存创建对象,进行深拷贝,返回的类型是非mutable类型的NSArray;进行mutableCopy操作时,也会重新申请内存创建对象,进行深拷贝,返回的类型是mutable类型的NSArray。
根据以上结论,我们观察如下属性声明:
1 | @property(nonatomic, copy)NSMutableArray *array; |
此处需要说明的是,无论你用什么类型的NSArray进行赋值,最终执行的都是copy操作,返回的都是非mutable类型的NSArray,而不是声明的mutable类型,进行赋值操作后不能再调用mutable类型的remove/add方法,否则会因为未找到消息而出现崩溃。
字符串类型NSString
接下来我们看一下对NSString类型进行copy/mutableCopy操作,会出现什么结果,我们逐一对一下情况进行分析:
操作对象为常亮字符串
1 | { |
1 | 对应输出为: |
通过@""
初始化的NSString的真正类型是__NSCFConstantString
,我们暂且称该类型的字符串为常量字符串,同时观察该类型的内存地址0x103713318
,我们可以猜测他说被分配在了常量区(类似于C语言中的const常量),而对常量字符串进行copy操作时,只是进行了简单的指针指向操作,不会引起计数+1,也不会重新创建对象,这是因为常量字符串只有在程序结束的时候才会被释放(至于苹果对该类型的常量字符串做了何等的优化有待我们继续深入探究),所以当我们用__weak
声明的对象指向常量字符串时,不会因为常量字符串没有被强引用而引起编译器警告;而对常量字符串进行mutableCopy操作时,会重新申请内存创建对象,进行深拷贝,返回的类型是__NSCFString
类型的字符串,即为NSMutableString类型。
操作对象为非可变字符串
1 | { |
1 | 对应输出为: |
通过stringWithFormat
创建的字符串的真实类型是NSTaggedPointerString
,观察该类型字符串的内存地址为0xa000000000000311
,这是一个很高位的地址,我们暂且将该种类型的字符串成为非mutable类型字符串;
在对非mutable类型字符串(即NSTaggedPointerString
)进行copy操作时,会引起引用计数+1,并返回非mutable类型的字符串;对其进行mutableCopy操作时,会重新申请内存创建对象,并返回mutable类型的字符串;
操作对象为可变字符串
1 | { |
1 | 对应输出为: |
通过stringWithFormat
创建的字符串的真实类型是__NSCFString
,观察该类型字符串内存地址为0x7febf4900120
,更像是被分配到了堆区,我们暂且称该类字符串为mutable类型字符串;
在对mutable类型的字符串(即__NSCFString
)进行copy操作时,也只会引起引用计数+1,并返回非mutable类型的字符串(注意此时与容器类型的区别);而对其进行mutableCopy操作时,会重新申请内存创建对象,并返回mutable类型的字符串;