美文网首页
iOS基础深入补完计划--属性strong&&c

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

作者: kirito_song | 来源:发表于2017-12-07 13:48 被阅读170次

    前文地址:《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方法。确实是如网上所言。

    WechatIMG213.jpeg
    • 所以、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的字符串》


    最后

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

    相关文章

      网友评论

          本文标题:iOS基础深入补完计划--属性strong&&c

          本文链接:https://www.haomeiwen.com/subject/txydixtx.html