iOS基础深入补完计划--属性strong&&c

2017-12-07  本文已影响170人  kirito_song

前文地址:《iOS基础深入补完计划》

在前文、我们提到了property中的关键字copy可以用来修饰不可变对象、以保护对象的封装性。
那么、copy和strong修饰的属性究竟有什么区别。为什么copy修饰之后、不受原变量的影响。

以下、将分析以下几点:


关于引用计数

先用mutable字符串进行测试:
  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
  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内的只针对想上。
  • 单纯对原对象增加了引用计数。

这两点、可以从变量地址上鉴别。


关于变量地址

  #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

打印:

  变量值地址: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方法。确实是如网上所言。

WechatIMG213.jpeg
  • 所以、copy属性不支持普通变量

因为普通变量本身就不能被修改。也不能使用copy方法


一些题外话

在测试地址的过程中、又出现两个问题。

NSCFConstantString

总而言之:

  • 以上两种方式生成的字符串、无论长短都是NSCFConstantString类型。
  • 引用计数无限大。
  • 在值相同的情况下可以直接用 ‘==’判定。
  • 经测试、值相同时、变量地址相同。都存在于栈内存上。(应该就是个常量字符串吧)
  • 其他objc对象类似的、在堆上分配内存。
  • 初始引用计数为1。

更多的测试、可以参阅这一片大佬的博客《NSString特性分析学习》

NSTaggedPointerString

我们可以抛开后面的String、单纯的来看TaggedPointer(标记指针)对象。因为除了NSString、NSNumber和NSDate也有相应的TaggedPointer对象。
简单来说:

  • TaggedPointer用于存储所需字节较小的变量型对象。
  • TaggedPointer用于将数据直接保存在指针地址本身中(指针的值不再单纯是地址了,而是真正的值)、借此不需要生成对象、节省了内存和效率。
  • TaggedPointer只是一个披着对象皮的普通变量而已。

参考《深入理解Tagged Pointer》《【译】采用Tagged Pointer的字符串》


最后

本文主要是自己的学习与总结。如果文内存在纰漏、万望留言斧正。如果不吝赐教小弟更加感谢。

上一篇下一篇

猜你喜欢

热点阅读