iOS Strong和Copy的区别

首先声明了两个NSString属性,如下:

@property(nonatomic, strong) NSString *strongStr;
@property(nonatomic, copy) NSString *copyyStr;

下面我们分别以NSString和NSMutableString举例

NSString场景一
 //  使用NSString直接赋值
NSString *originStr = [NSString stringWithFormat:@"hello,originStr"];

_strongStr = originStr;
_copyyStr = originStr;
    
NSLog(@"originStr 对象地址: %p ,对象指针地址:%p ,对象的值:%@", originStr, &originStr, originStr);
NSLog(@"strongStr 对象地址: %p ,对象指针地址: %p ,对象的值:%@", _strongStr, &_strongStr, _strongStr);
NSLog(@"copyyStr 对象地址: %p ,对象指针地址:%p ,对象的值:%@", _copyyStr, &_copyyStr, _copyyStr);

控制台输出

2018-09-06 09:06:31.741210+0800 PropertyDemo[2451:226039] originStr 对象地址: 0x60400025ff20 ,对象指针地址:0x7ffee80f9a48 ,对象的值:hello,originStr
2018-09-06 09:06:31.741346+0800 PropertyDemo[2451:226039] strongStr 对象地址: 0x60400025ff20 ,对象指针地址: 0x7f9561c12cc0 ,对象的值:hello,originStr
2018-09-06 09:06:31.741466+0800 PropertyDemo[2451:226039] copyyStr 对象地址: 0x60400025ff20 ,对象指针地址:0x7f9561c12cc8 ,对象的值:hello,originStr

结论:这种情况下,不管是用strong还是copy修饰的对象,其指向的地址都是originStr的地址。

NSString场景二
// 第二种场景:用NSString直接赋值
NSString *originStr = [NSString stringWithFormat:@"hello,originStr"];
    
self.strongStr = originStr;
self.copyyStr = originStr;
    
NSLog(@"originStr 对象地址: %p ,对象指针地址:%p ,对象的值:%@", originStr, &originStr, originStr);
NSLog(@"strongStr 对象地址: %p ,对象指针地址: %p ,对象的值:%@", _strongStr, &_strongStr, _strongStr);
NSLog(@"copyyStr 对象地址: %p ,对象指针地址:%p ,对象的值:%@", _copyyStr, &_copyyStr, _copyyStr);

控制台输出

2018-09-06 09:33:35.203366+0800 PropertyDemo[2804:257010] originStr 对象地址: 0x604000443f00 ,对象指针地址:0x7ffee73bba28 ,对象的值:hello,originStr
2018-09-06 09:33:35.203533+0800 PropertyDemo[2804:257010] strongStr 对象地址: 0x604000443f00 ,对象指针地址: 0x7f8c40e09240 ,对象的值:hello,originStr
2018-09-06 09:33:35.203645+0800 PropertyDemo[2804:257010] copyyStr 对象地址: 0x604000443f00 ,对象指针地址:0x7f8c40e09248 ,对象的值:hello,originStr

为什么用了self.copyyStr = originStr进行赋值,调用了setter方法之后,copyyStr指向的地址和originStr指向的地址还是相同的呢?

原因:这里的copy是浅拷贝,并没有生成新的对象

NSMutableString场景一
//  使用NSMutableString直接赋值
NSMutableString *originStr = [NSMutableString stringWithFormat:@"hello,originStr"];
    
_strongStr = originStr;
_copyyStr = originStr;
    
[originStr setString:@"hello,I changed"];
    
NSLog(@"originStr 对象地址: %p ,对象指针地址:%p ,对象的值:%@", originStr, &originStr, originStr);
NSLog(@"strongStr 对象地址: %p ,对象指针地址: %p ,对象的值:%@", _strongStr, &_strongStr, _strongStr);
NSLog(@"copyyStr 对象地址: %p ,对象指针地址:%p ,对象的值:%@", _copyyStr, &_copyyStr, _copyyStr);

控制台输出

2018-09-06 09:17:25.259841+0800 PropertyDemo[2652:239951] originStr 对象地址: 0x600000259290 ,对象指针地址:0x7ffeeeb8aa28 ,对象的值:hello,I changed
2018-09-06 09:17:25.259991+0800 PropertyDemo[2652:239951] strongStr 对象地址: 0x600000259290 ,对象指针地址: 0x7f86bae0d3d0 ,对象的值:hello,I changed
2018-09-06 09:17:25.260125+0800 PropertyDemo[2652:239951] copyyStr 对象地址: 0x600000259290 ,对象指针地址:0x7f86bae0d3d8 ,对象的值:hello,I changed

结论:不论是用strong还是copy修饰的对象,其指针指向的地址依然还是originStr的地址。
疑问:为什么_copyyStr的值会变成“hello,I changed”呢?不应该是“hello,originStr”吗?继续往下看

NSMutableString场景二
// 第二种场景:用NSMutableString直接赋值
NSMutableString *originStr = [NSMutableString stringWithFormat:@"hello,originStr"];
    
self.strongStr = originStr;
self.copyyStr = originStr;
    
[originStr setString:@"hello,I changed"];
    
NSLog(@"originStr 对象地址: %p ,对象指针地址:%p ,对象的值:%@", originStr, &originStr, originStr);
NSLog(@"strongStr 对象地址: %p ,对象指针地址: %p ,对象的值:%@", _strongStr, &_strongStr, _strongStr);
NSLog(@"copyyStr 对象地址: %p ,对象指针地址:%p ,对象的值:%@", _copyyStr, &_copyyStr, _copyyStr);

控制台输出

2018-09-06 09:23:02.871307+0800 PropertyDemo[2707:246046] originStr 对象地址: 0x60000024f630 ,对象指针地址:0x7ffee00bca28 ,对象的值:hello,I changed
2018-09-06 09:23:02.871465+0800 PropertyDemo[2707:246046] strongStr 对象地址: 0x60000024f630 ,对象指针地址: 0x7fb9cf40ea90 ,对象的值:hello,I changed
2018-09-06 09:23:02.871570+0800 PropertyDemo[2707:246046] copyyStr 对象地址: 0x60000024fcc0 ,对象指针地址:0x7fb9cf40ea98 ,对象的值:hello,originStr

疑问:self.copyyStr值:hello,originStr 终于是我们想要的结果
_copyyStr = originStr;和self.copyyStr = originStr;有什么区别?

原因:
用@property来声明属性变量时,编译器会自动为我们生成一个以下划线加属性名命名的实例变量(@synthesize copyyStr = _copyyStr),并且生成其对应的getter、setter方法。
1.self.copyyStr = originStr赋值时,会调用coppyStr的setter方法
2._copyyStr = originStr 赋值时给_copyyStr实例变量直接赋值,并不会调用copyyStr的setter方法
而在setter方法中有一个非常关键的语句:

_copyyStr = [copyyStr copy];

结论:
使用self.copyyStr = originStr 赋值时,调用copyyStr的setter方法,setter方法对传入的copyyStr做了次深拷贝生成了一个新的对象赋值给_copyyStr,所以_copyyStr指向的地址和对象值都不再和originStr相同。

总结
由上面的例子可以得出:

1.当原字符串是NSString时,由于是不可变字符串,所以,不管使用strong还是copy修饰,都是指向原来的对象,copy操作只是做了一次浅拷贝。
2.当源字符串是NSMutableString时,strong只是将源字符串的引用计数加1,而copy则是对原字符串做了次深拷贝,从而生成了一个新的对象,并且copy的对象指向这个新对象。

所以,如果源字符串是NSMutableString的时候,使用strong只会增加引用计数。但是copy会执行一次深拷贝,会造成不必要的内存浪费。而如果原字符串是NSString时,strong和copy效果一样,就不会有这个问题。
但是,我们一般声明NSString时,也不希望它改变,所以一般情况下,建议使用copy,这样可以避免NSMutableString带来的错误。


随便补充几个知识点:

简要阐述内存相关的关键字?
Strong:

指向并持有该对象,引用计数会加1。引用计数为0销毁,可以通过将变量强制赋值 nil 来进行销毁。

Weak:

指向但是并不持有该对象,引用计数不会加1。在 Runtime 中对该属性进行了相关操作,无需处理,可以自动销毁

assign:

assign主要用于修饰基本数据类型,例如NSInteger,CGFloat,存储在栈中,内存不用程序员管理

copy:

copy关键字和 strong类似,copy 多用于修饰有可变类型的不可变对象上 NSString,NSArray,NSDictionary上。

__unsafe_unretain:

__unsafe_unretain 类似于 weak ,但是当对象被释放后,指针依然保存着之前的地址,被释放后的地址变为 僵尸对象,访问被释放的地址就会出问题,所以说他是不安全的。

atomic:

这个属性是为了保证在多线程的情况下,编译器会自动生成一些互斥加锁的代码,避免该变量的读写不同步的问题。

nonatomic :

如果该对象无需考虑多线程的情况,这个属性会让编译器少生成一些互斥代码,可以提高效率。

__weak 和 __unsafe_unretain 的区别?

__unsafe_unretain 在指向的内存地址销毁后,指针本身并不会自动销毁,这也就造成了野指针,之后容易造成 Crash。__weak 在指向的内存销毁后,可以将指针变量置为 nil,这样更加安全。

__weak 修饰的变量在地址被释放后,为何被置为 nil?

在 Runtime 中专门维护了一个用于存储 weak指针变量的 Hash 表。这个表 key 是 weak指针所指向的内存地址,value 是指向这个内存地址的所有 weak指针,实际上是一个数组。释放时根据对象地址获取所有 weak指针 地址的数组,然后遍历这个数组把其中的数据设为 nil,最后把这个 对象 从weak表 中删除。

使用atomic一定是线程安全的吗?

atomic可以保证setter和getter存取的线程安全并不保证整个对象是线程安全的。
比如,声明一个NSMutableArray的原子属性array,此时self.array和self.array = otherArray都是线程安全的。但是,使用[self.array objectAtIndex:index]就不是线程安全的,需要用锁来保证线程安全性。

Demo:https://github.com/destinyzhao/PropertyDemo

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,902评论 5 468
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,037评论 2 377
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,978评论 0 332
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,867评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,763评论 5 360
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,104评论 1 277
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,565评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,236评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,379评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,313评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,363评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,034评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,637评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,719评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,952评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,371评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,948评论 2 341

推荐阅读更多精彩内容