NSString, NSArray, NSDictionary 关于strong 和 copy 使用

NSStringNSArray 在@property的时候,到底是用strong还是用copy修饰,这里依据自己的理解,做一个总结。

一. NSString

声明变量:

// strong  类型  NSString
@property (nonatomic, strong) NSString *strongString;
// copy    类型  NSString
@property (nonatomic, copy)   NSString *copyedString;

这里分别声明了一个用strong修饰的strongString变量和一个用copy修饰的copyedString变量。(注意: 不可以写成copyStr会报property follows cocoa naming convention for returning 'owned' objects意思是不能使用copy来作为开头命名,copy是cocoa用的)

NSString 赋值

NSString *tmpString = @"123456";

self.strongString = tmpString;
self.copyedString = tmpString;

NSLog(@"tmpString: %@, %p", tmpString, tmpString);
NSLog(@"self.strongString: %@, %p", self.strongString, self.strongString);
NSLog(@"self.copyedString: %@, %p", self.copyedString, self.copyedString);

查看输出结果

tmpString: 123456, 0x106074078
self.strongString: 123456, 0x106074078
self.copyedString: 123456, 0x106074078

根据输出的内存地址和内容,我们可以发现不管是strong修饰还是copy修饰,指向的都是同一个内存地址,也就是tmpString的地址。strongString和copyedString都只是对tmpString的引用,只会导致tmpString的计算器加1,并没有拷贝一份新的,tmpString的retainCount应该是3.

NSMutableString 赋值

NSMutableString *tmpMutableString = [[NSMutableString alloc] initWithString:@"123456"];

self.strongString = tmpMutableString;
self.copyedString = tmpMutableString;

NSLog(@"tmpMutableString: %@, %p", tmpMutableString, tmpMutableString);
NSLog(@"self.strongString: %@, %p", self.strongString, self.strongString);
NSLog(@"self.copyedString: %@, %p", self.copyedString, self.copyedString);

[tmpMutableString appendString:@"789"];

NSLog(@"tmpMutableString: %@, %p", tmpMutableString, tmpMutableString);
NSLog(@"self.strongString: %@, %p", self.strongString, self.strongString);
NSLog(@"self.copyedString: %@, %p", self.copyedString, self.copyedString);

输出打印结果

tmpMutableString: 123456, 0x600000260640
self.strongString: 123456, 0x600000260640
self.copyedString: 123456, 0xa003635343332316

tmpMutableString: 123456789, 0x600000260640
self.strongString: 123456789, 0x600000260640
self.copyedString: 123456, 0xa003635343332316

这时候我们可以看到,copy修饰的copyedString 字符串, 已经不再是简单的引用了,而是拷贝一个新的,让copyedStr指向了一个新的地址。此时tmpMutableString的ratainCount应该是2.

然后我们将[tmpMutableString appendString:@"789"];加上“789”,输出后,发现tmpMutableString和strongString会随之改变,但copyedString则不会随之变化。

分析

  1. 当源字符串是NSString类型时,由于是不可变字符串,所以,不管是使用strong还是copy修饰的字符串,都是指向源字符串,copy操作只是做了次浅拷贝。

  2. 当源字符串是NSMutableString时,strong修饰的字符串只是将源字符串的引用计算器加1,依旧指向源字符串,而copy修饰的字符串则是对源字符串做了次深拷贝,从而生成一个新的对象,self.copyedString的指针指向了这个新的对象。并且这个copy属性修饰的对象始终是NSString类型而不是NSMutableString类型,如果想让拷贝过来的对象可变,就需要使用mutableCopy。

总结

  • 如果源字符串是NSString类型,使用copy和strong修饰的字符串效果是一样的,都指向源字符串的内存地址。

  • 如果源字符串是NSMutableString的时候,给strong修饰的字符串赋值时,使用strong修饰的字符串只会增加引用计算器,但给copy修饰的字符串赋值时,copy修饰的字符串会执行一次深拷贝。

  • 一般我们声明NSString时,如果不希望它中途被改变,因为来源有可能是NSMutableString类型(可变类型)这种情况下要用copy,进行深拷贝。

  • 如果确定来源是NSString类型(不可变类型),这种情况下用strong比较好。因为copy修饰的NSString在进行set操作时,底层进行了这样的判断if ([str isMemberOfClass: [NSString class]]),如果来源是可变的,就进行一次深拷贝,如果是不可变的就和strong修饰一样,进行一次浅拷贝,
    当项目庞大时,有成百上千个NSString对象,多少会损耗app性能。

相遇.jpeg

二. NSArray

声明变量

// strong  类型 NSArray
@property (nonatomic, strong) NSArray  *strongArray;
// copy    类型 NSArray
@property (nonatomic, copy)   NSArray  *copyedArray;

NSArray 赋值

NSArray *tmpArray = [NSArray arrayWithObjects:@"1",@"2", nil];

self.strongArray = tmpArray;
self.copyedArray = tmpArray;

NSLog(@"tmpArray: %@, %p", tmpArray, tmpArray);
NSLog(@"self.strongArray: %@, %p", self.strongArray, self.strongArray);
NSLog(@"self.copyedArray: %@, %p", self.copyedArray, self.copyedArray);

输出结果

tmpArray: (
    1,
    2
), 0x610000037220
self.strongArray: (
    1,
    2
), 0x610000037220
 self.copyedArray: (
    1,
    2
), 0x610000037220

** NSMutableArray 赋值**

NSMutableArray *tmpArray = [NSMutableArray arrayWithObjects:@"1",@"2", nil];

self.strongArray = tmpArray;
self.copyedArray = tmpArray;

NSLog(@"tmpArray: %@, %p", tmpArray, tmpArray);
NSLog(@"self.strongArray: %@, %p", self.strongArray, self.strongArray);
NSLog(@"self.copyedArray: %@, %p", self.copyedArray, self.copyedArray);

[tmpArray addObject:@"3"];
NSLog(@"tmpArray: %@, %p", tmpArray, tmpArray);
NSLog(@"self.strongArray: %@, %p", self.strongArray, self.strongArray);
NSLog(@"self.copyedArray: %@, %p", self.copyedArray, self.copyedArray);

** 输出结果 **

 tmpArray: (
    1,
    2
), 0x600000052930
self.strongArray: (
    1,
    2
), 0x600000052930
self.copyedArray: (
    1,
    2
), 0x600000025c60



tmpArray: (
    1,
    2,
    3
), 0x600000052930
 self.strongArray: (
    1,
    2,
    3
), 0x600000052930
self.copyedArray: (
    1,
    2
), 0x600000025c60

从以上测试输出结果,我们可以很明显的看出,NSArray用strong或者copy修饰的原理与NSString是一致的。

  • 如果来源数组是NSArray,使用strong或者copy修饰效果是一样,都指向来源数组的内存地址。

  • 如果来源数组是NSMutableArray,使用strong只会增加引用计算,依旧指向来源数组的内存地址,但copy会执行一次深拷贝,指向新的内存地址。

  • 同样我们声明NSArray时,如果不希望它中途被改变,并且来源有可能是NSMutableArray时,这种情况下使用copy,进行深拷贝。

  • 如果确定来源是NSArray类型,这种情况下用strong类型。因为copy修饰的NSArray类型在进行set操作时,底层进行了这样的判断if ([str isMemberOfClass: [NSArray class]]),如果来源是可变的,就进行一次深拷贝,如果是不可变的就和strong修饰一样,进行一次浅拷贝,
    当项目庞大时,有成百上千个NSArray对象,多少会损耗app性能。

** 注意 **

如果来源数组是NSMutableArray,使用copy修饰的数组会执行一次深拷贝,指向新的内存地址。但是目标数组里面内容的内存地址和来源数组里面内容的内存地址还是一致的,如果改变了来源数组里面内容,目标数组也会跟着改变。

举个例子:

** 声明 PersonModel **

@interface PersonModel : NSObject
// 姓名
@property (nonatomic, copy) NSString *name;

// 年龄
@property (nonatomic, assign) NSInteger age;

// 地址
@property (nonatomic, copy) NSString *address;
@end


** 进行 赋值 **
PersonModel *tmpFirstPerModel = [[PersonModel alloc] init];
tmpFirstPerModel.name = @"Jack";
tmpFirstPerModel.age = 16;
tmpFirstPerModel.address = @"深圳市南山区白石洲下白石一坊9巷12号";

NSLog(@"tmpFirstPerModel: %p", tmpFirstPerModel);
NSMutableArray *tmpArray = [NSMutableArray arrayWithObjects:tmpFirstPerModel,@"2", nil];

self.strongArray = tmpArray;
self.copyedArray = tmpArray;


NSLog(@"tmpArray: %p, PersonModel: %p", tmpArray, [tmpArray objectAtIndex:0]);
NSLog(@"self.strongArray: %p, PersonModel: %p", self.strongArray, [self.strongArray objectAtIndex:0]);
NSLog(@"self.copyedArray: %p, PersonModel: %p", self.copyedArray, [self.copyedArray objectAtIndex:0]);


** 查看 输出 结果 **
tmpFirstPerModel: 0x61800002ffe0
tmpArray: 0x618000048ac0, PersonModel: 0x618000036960
self.strongArray: 0x618000048ac0, PersonModel: 0x618000036960
self.copyedArray: 0x618000036a60, PersonModel: 0x618000036960

从输出结果可以看出,虽然使用copy修饰的self.copyedArray数组进行了深拷贝,指向了新的内存地址,但是数组里面的PersonModel的指针还是和之前初始化的tmpFirstPerModel是一致的,如果你在来源数组中修改了,self.copyedArray里面的内容也会更改。

** 对personModel 进行修改**

tmpFirstPerModel.address = @"厦门市翔安区新店镇朝新路3号";

NSLog(@"tmpSecondPerModel.address: %@, %p", tmpFirstPerModel.address, tmpFirstPerModel);
NSLog(@"[self.strongArray objectAtIndex:0]: %@, %p",((PersonModel *)[self.strongArray objectAtIndex:0]).address, [tmpArray objectAtIndex:0]);
NSLog(@"[self.copyedArray objectAtIndex:0]: %@, %p",((PersonModel *)[self.copyedArray objectAtIndex:0]).address, [tmpArray objectAtIndex:0]);


** 查看 输出 结果 **
tmpSecondPerModel.address: 厦门市翔安区新店镇朝新路3号, 0x618000037020
[self.strongArray objectAtIndex:0]: 厦门市翔安区新店镇朝新路3号, 0x618000037020
[self.copyedArray objectAtIndex:0]: 厦门市翔安区新店镇朝新路3号, 0x618000037020

如果要避免这种情况,就必须对模型实现NSCopying和NSMutableCopying(如果支持可变类型)协议,然后实现- (id)copyWithZone:(NSZone *)zone 函数和- (id)mutableCopyWithZone:(NSZone *)zone ,接着遍历源数组进行copy添加到新数组中或者通过- (instancetype)initWithArray:(NSArray<ObjectType> *)array copyItems:(BOOL)flag函数,进行初始化拷贝。

** 代码展示 **

#pragma mark ---- NSCopying

- (id)copyWithZone:(NSZone *)zone {
    PersonModel *configModel = [[[self class] allocWithZone:zone] init];
    configModel.name = [self.name copyWithZone:zone];
    configModel.age = self.age;
    configModel.address = [self.address copyWithZone:zone];
    return configModel;
}

#pragma mark ---- NSMutableCopying

- (id)mutableCopyWithZone:(NSZone *)zone {
    return [self copyWithZone:zone];
}

// 遍历 拷贝 添加 方法
NSMutableArray *tmpMutableArray = [NSMutableArray array];
for (PersonModel *tmpPersonModel in tmpArray) {
    [tmpMutableArray addObject:[tmpPersonModel copy]];
}
self.copyedArray = tmpMutableArray;

// 初始化 方法 进行 内部 拷贝
self.copyedArray = [[NSArray alloc] initWithArray:tmpArray copyItems:YES];
樱花.jpeg

三. NSDictionary

** 声明变量 **

// strong  类型   NSDictionary
@property (nonatomic, strong) NSDictionary *strongDictionary;
// copy    类型   NSDictionary
@property (nonatomic, copy)   NSDictionary *copyedDictionary;

** NSDictionary 赋值 **

NSDictionary *tmpDict = [NSDictionary dictionaryWithObjectsAndKeys:@"Jake",@"1000",@"LinDa",@"1001", nil];

self.strongDictionary = tmpDict;
self.copyedDictionary = tmpDict;

NSLog(@"tmpDict: %@, %p", tmpDict, tmpDict);
NSLog(@"self.strongDictionary: %@, %p", self.strongDictionary, self.strongDictionary);
NSLog(@"self.copyedDictionary: %@, %p", self.copyedDictionary, self.copyedDictionary);

** 输出 结果 **

 tmpDict: {
    1000 = Jake;
    1001 = LinDa;
}, 0x610000267040

self.strongDictionary: {
    1000 = Jake;
    1001 = LinDa;
}, 0x610000267040

self.copyedDictionary: {
    1000 = Jake;
    1001 = LinDa;
}, 0x610000267040

** NSMutableDictionary 赋值 **

NSMutableDictionary *tmpMutableDict = [NSMutableDictionary dictionaryWithObjectsAndKeys:@"Jake",@"1000",@"LinDa",@"1001", nil];

self.strongDictionary = tmpMutableDict;
self.copyedDictionary = tmpMutableDict;

NSLog(@"tmpMutableDict: %@, %p", tmpMutableDict, tmpMutableDict);
NSLog(@"self.strongDictionary: %@, %p", self.strongDictionary, self.strongDictionary);
NSLog(@"self.copyedDictionary: %@, %p", self.copyedDictionary, self.copyedDictionary);

[tmpMutableDict setObject:@"Ailis" forKey:@"1002"];
NSLog(@"tmpMutableDict: %@, %p", tmpMutableDict, tmpMutableDict);
NSLog(@"self.strongDictionary: %@, %p", self.strongDictionary, self.strongDictionary);
NSLog(@"self.copyedDictionary: %@, %p", self.copyedDictionary, self.copyedDictionary);

** 输出 结果 **

 tmpMutableDict: {
    1000 = Jake;
    1001 = LinDa;
}, 0x6180000459d0

self.strongDictionary: {
    1000 = Jake;
    1001 = LinDa;
}, 0x6180000459d0

self.copyedDictionary: {
    1000 = Jake;
    1001 = LinDa;
}, 0x618000260d40


 tmpMutableDict: {
    1000 = Jake;
    1001 = LinDa;
    1002 = Ailis;
}, 0x6180000459d0

self.strongDictionary: {
    1000 = Jake;
    1001 = LinDa;
    1002 = Ailis;
}, 0x6180000459d0

 self.copyedDictionary: {
    1000 = Jake;
    1001 = LinDa;
}, 0x618000260d40

从以上测试输出结果,我们可以很明显的看出,NSDictionary用strong或者copy修饰的原理与NSString和NSArray是一致的。

同样的NSDicationary也存在着跟NSArray一样的问题。

如果来源数组是NSMutableDictionary,使用copy修饰的数组会执行一次深拷贝,指向新的内存地址。但是目标数组里面内容的内存地址和来源数组里面内容的内存地址还是一致的,如果改变了来源数组里面内容,目标数组也会跟着改变。

所以如果键值是类,要想进行内容赋值也必须实现NSCopying和NSMutableCopying(如果支持可变类型)协议,然后实现- (id)copyWithZone:(NSZone )zone 函数和- (id)mutableCopyWithZone:(NSZone )zone ,接着遍历源字典,根据key取出值进行copy添加到新字典里中或者通过- (instancetype)initWithDictionary:(NSDictionary<KeyType, ObjectType> *)otherDictionary copyItems:(BOOL)flag;函数,进行初始化拷贝。

** 注意 **

NSMutableArray *array = [NSMutableArray arrayWithObjects:@"1",@"2", nil];
NSMutableDictionary *dict = [NSMutableDictionary dictionary];
dict[array] = array;
for (NSMutableArray *key in dict.allKeys) {
    NSLog(@"%@", key.class);
    NSLog(@"%@", [dict[key] class]);
}

** 查看 输出 结果 **

key.class: __NSArrayI
[dict[key] class]: __NSArrayM

因为NSMutableDictionary 在 set key-value 的时候会把 key copy 一下,NSMutableArray 被 copy 成 NSArray 了,也就对应类族里面__NSArrayI。

四. 最后

送上一张喜欢的图片:

五厘米.jpeg

大家有兴趣可以看一下,如果觉得不错,麻烦给个喜欢,若发现问题请及时反馈,谢谢!

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

推荐阅读更多精彩内容

  • 本文为转载: 作者:zyydeveloper 链接:http://www.jianshu.com/p/5f776a...
    Buddha_like阅读 846评论 0 2
  • 307、setValue:forKey和setObject:forKey的区别是什么? 答:1, setObjec...
    AlanGe阅读 1,515评论 0 1
  • 点心灯 等轮回几世修 初识起风雅 情谊只为你留恋 普一曲白首韶华 描摹山水人家 煮一壶清茶 摘一株梅花 吟翠萧 反...
    栩辰徉阅读 273评论 3 12
  • 透析记2017‖1.17 今日透析。 早上六点半起床,戴上手套和口罩还有雨伞,出发去医院。 肆略了好长一段时间的雾...
    小棕榈阅读 592评论 0 0
  • 生活中,我是一个想象力极其天马行空的一个俗人,也是看倦了来来回回。才有一些自我的发言观,那么,说到二十一世纪,大多...
    猫命阅读 190评论 0 0