写在前面
关于copy
、mutableCopy
和strong
,一直想好好整理整理,但是因为各种原因搁置了。今天上班时发现老代码中因为这个问题出现了一个特别恶心的大坑,让我下定决心写下这篇博文。如果你认为自己没掌握copy
相关知识,建议看看此文,这里会有你需要的;如果你认为自己掌握了copy
相关知识,也建议看看此文,也许这里有你感兴趣的东西。
首先和大家回顾一下有关copy的两个概念——浅拷贝(Shallow Copy)
和深拷贝(Deep Copy)
。
浅拷贝:只拷贝指针,不拷贝内容(不用为内容分配新的内存空间);
深拷贝:同时拷贝指针和内容,即分配一块新的内存,将原内容复制一份到该内存中,并生成指向该内存的新指针。(这里谢谢@灿烂天空的提醒,原来的解释可能不太准确,特此修正)
通俗点解释就是,已知一条路,通往罗马,浅拷贝就是新建一条通往这个罗马的路。而深拷贝则是选一个地方,按照原来的罗马新建一个(罗马内部包含的所有东西全部照旧的新建,比如家居、文化等等),并修一条通往新罗马的路,这样原来罗马怎么样就跟新罗马没关系了。
In the case of collection objects, a shallow copy means that a new collection object is created, but the contents of the original collection are not duplicated—only the object references are copied to the new container.
A deep copy duplicates the compound object as well as the contents of all of its contained objects.
四种copy情况
不可变非集合类对象的copy
直接上代码:
NSString *immutableStr = @"KFA_test1";
NSString *copyImmutableStr = [immutableStr copy];
NSString *mutableCopyImmutableStr = [immutableStr mutableCopy];
NSLog(@"\\nimmutableStr:%p-%p\\ncopyImmutableStr:%p-%p\\nmutableImmutableStr:%p-%p",immutableStr,&immutableStr,copyImmutableStr,©ImmutableStr,mutableCopyImmutableStr,&mutableCopyImmutableStr);
复制代码
运行结果
immutableStr:0x103426098-0x7fff5c7dba08
copyImmutableStr:0x103426098-0x7fff5c7dba00
mutableImmutableStr:0x7ffd99d34ce0-0x7fff5c7db9f8
复制代码
可以发现,对不可变非集合类对象进行copy
时,只拷贝了指针,内容不拷贝。对不可变非集合类对象进行mutableCopy
时,同时拷贝了指针和内容。
可变非集合类对象的copy
照例上代码:
NSMutableString *mutableStr = [@"KFA_test2" mutableCopy];
NSString *copyImmutableStr = [mutableStr copy];
NSString *mutableCopyImmutableStr = [mutableStr mutableCopy];
NSLog(@"\\nmutableStr:%p-%p\\ncopyImmutableStr:%p-%p\\nmutableImmutableStr:%p-%p",mutableStr,&mutableStr,copyImmutableStr,©ImmutableStr,mutableCopyImmutableStr,&mutableCopyImmutableStr);
复制代码
运行结果:
mutableStr:0x7feb8bc1c940-0x7fff5610fa08
copyImmutableStr:0xa34cf2e0400c1289-0x7fff5610fa00
mutableImmutableStr:0x7feb8bc1f320-0x7fff5610f9f8
复制代码
从结果可以发现,对可变非集合类对象进行copy
和mutableCopy
,都同时拷贝指针和内容。
不可变集合类对象的copy
这里以数组为例:
NSArray *immutableArr = @[@"KFA_test3"];
NSString *immu = immutableArr.firstObject;
NSArray *copyImmutableArr = [immutableArr copy];
NSString *copyImmu = copyImmutableArr.firstObject;
NSArray *mutableCopyImmutableArr = [immutableArr mutableCopy];
NSString *mutableCopyImu = mutableCopyImmutableArr.firstObject;
NSLog(@"\\nimmutableArr:%p-%p\\ncopyImmutableArr:%p-%p\\nmutableCopyImmutableArr:%p-%p\\nimmu:%p-%p\\ncopyImmu:%p-%p\\nmutableCopyImu:%p-%p",immutableArr,&immutableArr,copyImmutableArr,©ImmutableArr,mutableCopyImmutableArr,&mutableCopyImmutableArr,immu,&immu,copyImmu,©Immu,mutableCopyImu,&mutableCopyImu);
复制代码
运行结果:
immutableArr:0x7fa44a7087d0-0x7fff576619d0
copyImmutableArr:0x7fa44a7087d0-0x7fff576619c0
mutableCopyImmutableArr:0x7fa44a703370-0x7fff576619b0
immu:0x1085a0118-0x7fff576619c8
copyImmu:0x1085a0118-0x7fff576619b8
mutableCopyImu:0x1085a0118-0x7fff576619a8
复制代码
不难发现,对不可变集合类对象进行copy
时,只拷贝指针,不拷贝内容。对不可变集合类对象进行mutableCopy
时,同时拷贝指针和内容,但是这里的内容只针对集合对象本身,对于集合内的元素只拷贝指针,不拷贝内容。
可变集合类对象的copy
还是以数组为例:
NSArray *mutableArr = [@[@"KFA_test3"] mutableCopy];
NSString *mu = mutableArr.firstObject;
NSArray *copyImmutableArr = [mutableArr copy];
NSString *copyImmu = copyImmutableArr.firstObject;
NSArray *mutableCopyImmutableArr = [mutableArr mutableCopy];
NSString *mutableCopyImu = mutableCopyImmutableArr.firstObject;
NSLog(@"\\nmutableArr:%p-%p\\ncopyImmutableArr:%p-%p\\nmutableCopyImmutableArr:%p-%p\\nmu:%p-%p\\ncopyImmu:%p-%p\\nmutableCopyImu:%p-%p",mutableArr,&mutableArr,copyImmutableArr,©ImmutableArr,mutableCopyImmutableArr,&mutableCopyImmutableArr,mu,&mu,copyImmu,©Immu,mutableCopyImu,&mutableCopyImu);
复制代码
运行结果:
mutableArr:0x7feacbd0d250-0x7fff521409d0
copyImmutableArr:0x7feacbd06ef0-0x7fff521409c0
mutableCopyImmutableArr:0x7feacbd0c2a0-0x7fff521409b0
mu:0x10dac1118-0x7fff521409c8
copyImmu:0x10dac1118-0x7fff521409b8
mutableCopyImu:0x10dac1118-0x7fff521409a8
复制代码
我们发现,对可变集合类对象进行copy
和mutableCopy
时,都同时拷贝指针和内容。这里的内容跟上文一样只针对集合对象本身,对于集合内部的元素只拷贝指针,不拷贝内容。
strong和copy
strong和copy下的字符串
先添加两个字符串ivar,分别用strong和copy修饰:
@property (nonatomic, strong) NSString *testStrongStr;
@property (nonatomic, copy) NSString *testCopyStr;
复制代码
然后写个测试方法:
- (void)testCopyAndStrongForString {
NSString *immutableStr = @"KFA_test4";
self.testStrongStr = immutableStr;
self.testCopyStr = immutableStr;
NSLog(@"\\nimmutableStr:%@\\nself.testStrongStr:%@\\nself.testCopyStr:%@",immutableStr,self.testStrongStr,self.testCopyStr);
immutableStr = @"KFA_test4_change";
NSLog(@"\\nimmutableStr:%@\\nself.testStrongStr:%@\\nself.testCopyStr:%@",immutableStr,self.testStrongStr,self.testCopyStr);
NSMutableString *mutableStr = [@"KFA_test5" mutableCopy];
self.testStrongStr = mutableStr;
self.testCopyStr = mutableStr;
NSLog(@"\\nmutableStr:%@\\nself.testStrongStr:%@\\nself.testCopyStr:%@",mutableStr,self.testStrongStr,self.testCopyStr);
[mutableStr appendString:@"_change"];
NSLog(@"\\nmutableStr:%@\\nself.testStrongStr:%@\\nself.testCopyStr:%@",mutableStr,self.testStrongStr,self.testCopyStr);
}
复制代码
运行结果:
immutableStr:KFA_test4
self.testStrongStr:KFA_test4
self.testCopyStr:KFA_test4
immutableStr:KFA_test4_change
self.testStrongStr:KFA_test4
self.testCopyStr:KFA_test4
mutableStr:KFA_test5
self.testStrongStr:KFA_test5
self.testCopyStr:KFA_test5
mutableStr:KFA_test5_change
self.testStrongStr:KFA_test5_change
self.testCopyStr:KFA_test5
复制代码
从结果可以看出,当赋值对象是不可变的字符串时,strong
和copy
的效果一样,因为当赋值对象为不可变的字符串时,copy
修饰的ivar进行拷贝相当于上文中的对不可变对象进行copy
,只拷贝指针,不考虑内容。但是当赋值对象为可变的字符串时,当赋值对象发生变化时,strong
修饰的ivar会跟着变,copy
修饰的ivar进行了拷贝(参考上文中对可变对象进行copy
),所以不会变。这里可能有同学会有疑惑,为什么明明KFA_test4变成了KFA_test4_change,内容变了,而self.testStrongStr和self.testCopyStr的内容没变,这是因为KFA_test4变成KFA_test4_change只是因为immutableStr指针进行了重指向指向了KFA_test4_change,而内容KFA_test4属于常量,存在栈区,本身并没有变。所以在修饰字符串时为了数据的安全,建议用copy
,而不用strong
。
strong和copy下的数组
同样,先添加两个数组ivar,分别用strong
和copy
修饰:
@property (nonatomic, strong) NSArray *testStrongArr;
@property (nonatomic, copy) NSArray *testCopyArr;
复制代码
另外再创建一个model类,有一个number属性:
@interface KFACopyModel : NSObject
@property (nonatomic, copy) NSString *number;
@end
复制代码
再写两个函数(等会测试方法里会用到),用来同时打印三个数组(里面的元素):
void KFALog(NSArray *array1, NSArray *array2, NSArray *array3) {
NSString *string1 = KFANSStringFromArray(array1);
NSString *string2 = KFANSStringFromArray(array2);
NSString *string3 = KFANSStringFromArray(array3);
NSLog(@"\\n\\%@\\n%@\\n%@",string1,string2,string3);
}
NSString *KFANSStringFromArray(NSArray *array) {
NSMutableString *string = nil;
for (NSString *objc in array) {
NSString *objcStr = nil;
if ([objc isKindOfClass:[NSString class]]) {
objcStr = (NSString *)objc;
}else if ([objc isKindOfClass:[KFACopyModel class]]) {
KFACopyModel *model = (KFACopyModel *)objc;
objcStr = model.number;
}
if (string == nil) {
string = [objcStr mutableCopy];
}else {
[string appendFormat:@"_%@",objcStr];
}
}
return string;
}
复制代码
然后就是测试方法了:
- (void)testCopyAndStrongForArray {
NSArray *immutableArr = @[@"KFA_test6"];
self.testStrongArr = immutableArr;
self.testCopyArr = immutableArr;
KFALog(immutableArr,self.testStrongArr,self.testCopyArr);
immutableArr = @[@"KFA_test6_change"];
KFALog(immutableArr,self.testStrongArr,self.testCopyArr);
NSMutableArray *mutableArr = [@[@"KFA_test7"] mutableCopy];
self.testStrongArr = mutableArr;
self.testCopyArr = mutableArr;
KFALog(mutableArr, self.testStrongArr, self.testCopyArr);
[mutableArr replaceObjectAtIndex:0 withObject:@"KFA_test7_change"];
KFALog(mutableArr, self.testStrongArr, self.testCopyArr);
KFACopyModel *model1 = [[KFACopyModel alloc] init];
model1.number = @"13789892310";
NSArray *immutableArr_ = @[model1];
self.testStrongArr = immutableArr_;
self.testCopyArr = immutableArr_;
KFALog(immutableArr_, self.testStrongArr, self.testCopyArr);
model1.number = @"137****2310";
KFALog(immutableArr_, self.testStrongArr, self.testCopyArr);
KFACopyModel *model2 = [[KFACopyModel alloc] init];
model2.number = @"13982650942";
NSArray *mutableArr_ = @[model2];
self.testStrongArr = mutableArr_;
self.testCopyArr = mutableArr_;
KFALog(mutableArr_, self.testStrongArr, self.testCopyArr);
model2.number = @"139****0942";
KFALog(mutableArr_, self.testStrongArr, self.testCopyArr);
}
复制代码
运行结果:
KFA_test6
KFA_test6_change
KFA_test6
KFA_test6
KFA_test7
KFA_test7
KFA_test7
KFA_test7_change
KFA_test7_change
KFA_test7
13789892310
13789892310
13789892310
137****2310
137****2310
137****2310
13982650942
13982650942
13982650942
139****0942
139****0942
139****0942
复制代码
我们发现一个有意思的问题,当数组里元素为字符串时,结果跟咱们预料的一样的,但是当数组的元素为KFACopyModel对象时,结果跟咱们预料有点出入了。这里就得回到上文说的四种情况了。对集合类对象进行copy
和mutableCopy
时,即便拷贝内容,拷贝的也是集合对象本身,对于集合内的元素只拷贝了指针,不拷贝内容。所以当数组里的元素KFACopyModel对象的number属性发生变化时,拷贝的数组里元素也会跟着变化。
写在最后
通过上文我们可以总结两点:
- 对于凡是具有可变子类的类,如NSString有子类NSMutableString,NSArray有子类NSMutableArray,都用
copy
进行修饰,不用strong
。但是对可变类,如NSMutableArray,应该用strong
,不用copy
,不然就会造成明明定义一个可变数组却不能调用可变数组的方法的后果。 - 给数组ivar赋值时,为了数组的安全,最好用一个临时的数组对象(此对象不会在其他地方用到),更不能用一个数组ivar来给另一个数组ivar赋值。
本文用的代码全在这里,可以点击下载。欢迎拍砖和转发。