NSString 和 NSArray 在@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则不会随之变化。
分析
当源字符串是NSString类型时,由于是不可变字符串,所以,不管是使用strong还是copy修饰的字符串,都是指向源字符串,copy操作只是做了次浅拷贝。
当源字符串是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性能。
二. 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];
三. 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。
四. 最后
送上一张喜欢的图片:
大家有兴趣可以看一下,如果觉得不错,麻烦给个喜欢,若发现问题请及时反馈,谢谢!