前文地址:《iOS基础深入补完计划》
在前文、我们提到了property中的关键字copy可以用来修饰不可变对象、以保护对象的封装性。
那么、copy和strong修饰的属性究竟有什么区别。为什么copy修饰之后、不受原变量的影响。
以下、将分析以下几点:
- 引用计数
- 变量地址
- copy的具体实现
- 顺带分析两个字符串的类型NSTaggedPointerString/NSCFConstantString
关于引用计数
先用mutable字符串进行测试:
-
strong修饰
NSMutableString * mOStr = [NSMutableString stringWithFormat:@"1234567890"]; printf("mOStr原始引用前计数: %ld\n",CFGetRetainCount((__bridge CFTypeRef)(mOStr))); self.strongedStr = mOStr; printf("mOStr被strong引用后计数: %ld\n",CFGetRetainCount((__bridge CFTypeRef)(mOStr)));
结果
mOStr原始引用前计数: 2 mOStr被strong引用后计数: 3 _strongedStr计数: 3
-
copy修饰
NSMutableString * mOStr = [NSMutableString stringWithFormat:@"1234567890"]; printf("mOStr原始引用前计数: %ld\n",CFGetRetainCount((__bridge CFTypeRef)(mOStr))); self.copyedStr = mOStr; printf("mOStr被copy引用后计数: %ld\n",CFGetRetainCount((__bridge CFTypeRef)(mOStr)));
打印:
mOStr原始引用前计数: 2 mOStr被copy引用后计数: 2 _copyedStr计数: 1
再用immutable字符串进行测试:
NSString * mOStr = [NSString stringWithFormat:@"1234567890"]; printf("mOStr原始引用前计数: %ld\n",CFGetRetainCount((__bridge CFTypeRef)(mOStr))); self.strongedStr = mOStr; printf("_mOStr被strong引用后计数: %ld\n",CFGetRetainCount((__bridge CFTypeRef)(mOStr))); printf("_strongedStr计数: %ld\n",CFGetRetainCount((__bridge CFTypeRef)(_strongedStr))); self.copyedStr = mOStr; printf("_mOStr被copy引用后计数: %ld\n",CFGetRetainCount((__bridge CFTypeRef)(mOStr))); printf("_copyedStr计数: %ld\n",CFGetRetainCount((__bridge CFTypeRef)(_copyedStr)));
打印:
mOStr原始引用前计数: 2 _mOStr被strong引用后计数: 3 _strongedStr计数: 3 _mOStr被copy引用后计数: 4 _copyedStr计数: 4
结论:
- 在mutable字符串下。使用strong修饰会增加原对象引用计数、使用copy修饰则不会。
- 在immutable下。使用strong/copy修饰都会增加原对象引用计数。
- 除了字符串以外、Array/Dictionary的测试结果相同。
至于为什么在immutable下、引用都会+1。有两种可能。
- 像__block一样、将原对象转移到堆中并且将全部指针以及计数转移到block内的只针对想上。
- 单纯对原对象增加了引用计数。
这两点、可以从变量地址上鉴别。
关于变量地址
-
先用mutable字符串进行测试:
#define TLog(prefix,Obj) {NSLog(@"变量值地址:%p, 指向对象值:%@, 变量类型:%@--%@",Obj,Obj,[Obj class],prefix);} @interface ViewController () @property (nonatomic,strong,readwrite) NSString * strongedStr; @property (nonatomic,copy,readwrite) NSString * copyedStr; @end @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; // Do any additional setup after loading the view, typically from a nib. NSMutableString * mOStr = [NSMutableString stringWithFormat:@"1234567890"]; TLog(@"原对象mOStr", mOStr); self.strongedStr = mOStr; self.copyedStr = mOStr; TLog(@"改变前的原对象mOStr", mOStr); TLog(@"原对象改变前的strongedStr",_strongedStr); TLog(@"原对象改变前的copyedStr",_copyedStr); [mOStr appendString:@"lalala"]; TLog(@"改变后的原对象mOStr", mOStr); TLog(@"原对象改变后的strongedStr",_strongedStr); TLog(@"原对象改变后的copyedStr",_copyedStr); }
打印:
变量值地址:0x604000247500, 指向对象值:1234567890, 变量类型:__NSCFString--原对象mOStr 变量值地址:0x604000247500, 指向对象值:1234567890, 变量类型:__NSCFString--改变前的原对象mOStr 变量值地址:0x604000247500, 指向对象值:1234567890, 变量类型:__NSCFString--原对象改变前的strongedStr 变量值地址:0x6040000346e0, 指向对象值:1234567890, 变量类型:__NSCFString--原对象改变前的copyedStr 变量值地址:0x604000247500, 指向对象值:1234567890lalala, 变量类型:__NSCFString--改变后的原对象mOStr 变量值地址:0x604000247500, 指向对象值:1234567890lalala, 变量类型:__NSCFString--原对象改变后的strongedStr 变量值地址:0x6040000346e0, 指向对象值:1234567890, 变量类型:__NSCFString--原对象改变后的copyedStr
-
再用immutable字符串进行测试:
打印:
变量值地址:0x60000022cd20, 指向对象值:1234567890, 变量类型:__NSCFString--原对象mOStr 变量值地址:0x60000022cd20, 指向对象值:1234567890, 变量类型:__NSCFString--改变前的原对象mOStr 变量值地址:0x60000022cd20, 指向对象值:1234567890, 变量类型:__NSCFString--原对象改变前的strongedStr 变量值地址:0x60000022cd20, 指向对象值:1234567890, 变量类型:__NSCFString--原对象改变前的copyedStr 变量值地址:0x106a75130, 指向对象值:lalala, 变量类型:__NSCFConstantString--改变后的原对象mOStr 变量值地址:0x60000022cd20, 指向对象值:1234567890, 变量类型:__NSCFString--原对象改变后的strongedStr 变量值地址:0x60000022cd20, 指向对象值:1234567890, 变量类型:__NSCFString--原对象改变后的copyedStr
结论:
- 在mutable字符串下。使用strong修饰引用指向原变量内存地址
copy则会在新地址上copy出一个变量- 在immutable下。使用strong/copy修饰都不会生成新的变量、所以指针是指向了同一个地址并且将计数+1。
- 无论声明的属性可变与否、copy声明让你得到一份不可变的副本。
- 除了字符串以外、Array/Dictionary的测试结果相同。
copy的具体实现
网上都说是在set内部实现了[xxx copy]动作。
自己试了试、提示未式袭击案copyWithZone方法。确实是如网上所言。
所以、copy属性不支持普通变量
因为普通变量本身就不能被修改。也不能使用copy方法
一些题外话
在测试地址的过程中、又出现两个问题。
- NSCFConstantString是个什么东西。
- NSTaggedPointerString又是个什么。(如果将str的内容由1234567890缩短成1234。NSCFString就会变成NSTaggedPointerString类型)
NSCFConstantString
总而言之:
-
NSCFConstantString类型的出现、取决于你这个字符串的创建方式。具体点:
NSString * mOStr1 = @"1234"; NSString * mOStr2 = [NSString stringWithString:@"1234"]; NSString * mOStr3 = [NSString stringWithFormat:@"1234"];//这个是正常生成的、是个对象。
-
先说第1、2种方式声明的字符串:
- 以上两种方式生成的字符串、无论长短都是NSCFConstantString类型。
- 引用计数无限大。
- 在值相同的情况下可以直接用 ‘==’判定。
- 经测试、值相同时、变量地址相同。都存在于栈内存上。(应该就是个常量字符串吧)
-
然后、最后一种方式生成的字符串:
- 其他objc对象类似的、在堆上分配内存。
- 初始引用计数为1。
更多的测试、可以参阅这一片大佬的博客《NSString特性分析学习》
NSTaggedPointerString
我们可以抛开后面的String、单纯的来看TaggedPointer(标记指针)对象。因为除了NSString、NSNumber和NSDate也有相应的TaggedPointer对象。
简单来说:
- TaggedPointer用于存储所需字节较小的变量型对象。
- TaggedPointer用于将数据直接保存在指针地址本身中(指针的值不再单纯是地址了,而是真正的值)、借此不需要生成对象、节省了内存和效率。
- TaggedPointer只是一个披着对象皮的普通变量而已。
参考《深入理解Tagged Pointer》、《【译】采用Tagged Pointer的字符串》
最后
本文主要是自己的学习与总结。如果文内存在纰漏、万望留言斧正。如果不吝赐教小弟更加感谢。