深拷贝与浅拷贝是在内存管理中非常重要的概念,理解好深拷贝和浅拷贝也有助于加深对iOS的内存管理的理解。
深拷贝与浅拷贝的概念
浅拷贝就是内存地址的复制,拷贝指向原来对象的指针,使原对象引用计数+1。
可以理解为创建了一个指向原对象的新指针,并没有创建一个全新的对象。
深拷贝就是指拷贝对象的具体内容,拷贝出来后两个对象虽然存的值是相同的,但是内存地址不一样,两个对象也互不影响。
那么怎么才能判别是深拷贝还是浅拷贝呢,有一个很简单的办法就是通过打印对象的内存地址来判别是否是同一个对象,如果内存地址不同则代表是新开辟的内存空间,为深拷贝;如果内存地址相同则代表没有开辟新的内存空间,为浅拷贝。
下面就以数组为例来验证一下不同情况的深拷贝与浅拷贝:
装有基本类型元素的数组拷贝
NSArray与Copy
NSString *str1 = @"1";
NSString *str2 = @"2";
NSArray *array1 = [[NSArray alloc] initWithObjects:str1, str2, nil];
id array2 = [array1 copy];
[array2 isKindOfClass:[NSMutableArray class]] ? NSLog(@"array2为可变数组") : NSLog(@"array2为不可变数组");
NSLog(@"array1 address:%p, array2 address:%p", array1, array2);
打印结果如下:
2021-01-12 09:35:57.753827+0800 ShallowCopy&DeepCopy[6036:27261356] array2为不可变数组
2021-01-12 09:35:57.753966+0800 ShallowCopy&DeepCopy[6036:27261356] array1 address:0x600002734be0, array2 address:0x600002734be0
结论:不可变数组进行copy,不会开辟新的内存空间也不会生成不可变对象,array1与array2指向同一个内存地址。
NSMutableArray与copy
NSMutableArray *array1 = [[NSMutableArray alloc] initWithObjects:@"1", @"2", nil];
id array2 = [array1 copy];
[array2 isKindOfClass:[NSMutableArray class]] ? NSLog(@"array2为可变数组") : NSLog(@"array2为不可变数组");
array1[0] = @"3";
NSLog(@"array1 address:%p, array2 address:%p", array1, array2);
NSLog(@"array1:%@, array2:%@", array1, array2);
打印结果如下:
2021-01-12 09:44:34.714372+0800 ShallowCopy&DeepCopy[9999:27285362] array2为不可变数组
2021-01-12 09:44:34.714558+0800 ShallowCopy&DeepCopy[9999:27285362] array1 address:0x600000657cc0, array2 address:0x600000805320
2021-01-12 09:44:34.714781+0800 ShallowCopy&DeepCopy[9999:27285362] array1:(
3,
2
), array2:(
1,
2
)
结论:对可变数组进行copy,会开辟新的内存空间,生成一个新的不可变数组,两个数组互不影响。
NSArray与mutableCopy
NSArray *array1 = [[NSArray alloc] initWithObjects:@"1", @"2", nil];
id array2 = [array1 mutableCopy];
[array2 isKindOfClass:[NSMutableArray class]] ? NSLog(@"array2为可变数组") : NSLog(@"array2为不可变数组");
array2[0] = @"3";
NSLog(@"array1 address:%p, array2 address:%p", array1, array2);
NSLog(@"array1:%@, array2:%@", array1, array2);
打印结果为:
2021-01-12 09:48:30.798739+0800 ShallowCopy&DeepCopy[11740:27295204] array2为可变数组
2021-01-12 09:48:30.798892+0800 ShallowCopy&DeepCopy[11740:27295204] array1 address:0x600000344e60, array2 address:0x600000d34c90
2021-01-12 09:48:30.799102+0800 ShallowCopy&DeepCopy[11740:27295204] array1:(
1,
2
), array2:(
3,
2
)
结论:对不可变数组进行mutableCopy,会开辟新的内存空间,生成一个可变数组、两个数组互不影响。
NSMutableArray与mutableCopy
NSMutableArray *array1 = [[NSMutableArray alloc] initWithObjects:@"1", @"2", nil];
id array2 = [array1 mutableCopy];
[array2 isKindOfClass:[NSMutableArray class]] ? NSLog(@"array2为可变数组") : NSLog(@"array2为不可变数组");
array1[0] = @"3";
NSLog(@"array1 address:%p, array2 address:%p", array1, array2);
NSLog(@"array1:%@, array2:%@", array1, array2);
打印结果为:
2021-01-12 09:52:21.895913+0800 ShallowCopy&DeepCopy[13524:27305490] array2为可变数组
2021-01-12 09:52:21.896071+0800 ShallowCopy&DeepCopy[13524:27305490] array1 address:0x600002a6d7d0, array2 address:0x600002a6db90
2021-01-12 09:52:21.896230+0800 ShallowCopy&DeepCopy[13524:27305490] array1:(
3,
2
), array2:(
1,
2
)
结论:可变数组进行mutableCopy,会开辟新的内存空间、生成一个可变数组,两个数组互不影响。
下面就是总结好的copy和mutableCopy的结果
装有可变字符串的数组拷贝
装有基本类型的数组拷贝我们已经尝试过了,那么将字符串换成可变字符串之后还会遵循上述规则么?
NSMutableString * str1 = [[NSMutableString alloc] initWithString:@"1"];
NSMutableString * str2 = [[NSMutableString alloc] initWithString:@"2"];
NSMutableArray *array1 = [NSMutableArray arrayWithObjects:str1, str2, nil];
id array2 = [array1 mutableCopy];
[array2 isKindOfClass:[NSMutableArray class]] ? NSLog(@"array2为可变数组") : NSLog(@"array2为不可变数组");
[str1 appendString:@"3"];
NSLog(@"array1 address:%p, array2 address:%p", array1, array2);
NSLog(@"array1:%@, array2:%@", array1, array2);
打印结果为:
2021-01-12 10:18:36.709537+0800 ShallowCopy&DeepCopy[25809:27375890] array2为可变数组
2021-01-12 10:18:36.709683+0800 ShallowCopy&DeepCopy[25809:27375890] array1 address:0x600002514e10, array2 address:0x600002514d80
2021-01-12 10:18:36.709912+0800 ShallowCopy&DeepCopy[25809:27375890] array1:(
13,
2
), array2:(
13,
2
)
可以看到array2为可变数组,array1和array2的内存地址不同,证明了数组确实进行了深拷贝,但是对str1进行append操作却影响到了这两个数组。这是因为什么?我们来打印一下array1和array2内部数据的内存地址。
for (NSMutableString *str in array1) {
NSLog(@"%p",str);
}
NSLog(@"==========");
for (NSMutableString *str in array2) {
NSLog(@"%p",str);
}
打印结果如下:
2021-01-12 10:21:32.943522+0800 ShallowCopy&DeepCopy[27325:27385477] 0x6000003c0c60
2021-01-12 10:21:32.943650+0800 ShallowCopy&DeepCopy[27325:27385477] 0x6000003c0ba0
2021-01-12 10:21:32.943799+0800 ShallowCopy&DeepCopy[27325:27385477] ==========
2021-01-12 10:21:32.943926+0800 ShallowCopy&DeepCopy[27325:27385477] 0x6000003c0c60
2021-01-12 10:21:32.944042+0800 ShallowCopy&DeepCopy[27325:27385477] 0x6000003c0ba0
结果发现两个数组内部存储的可变字符串的内存地址相同。数组本身进行了深拷贝但可变字符串只是进行了浅拷贝。
装有模型元素的数组拷贝
装有基本类型的数组拷贝我们已经尝试过了,那么装有模型元素的数组还会遵循上述规则么,我们来看一下
装有模型的可变数组的mutableCopy
Animal *animal1 = [[Animal alloc] init];
animal1.age = @"1";
animal1.name = @"animal1";
animal1.sex = 1;
NSMutableArray *array1 = [NSMutableArray arrayWithObjects:animal1, nil];
id array2 = [array1 mutableCopy];
[array2 isKindOfClass:[NSMutableArray class]] ? NSLog(@"array2为可变数组") : NSLog(@"array2为不可变数组");
NSLog(@"array1 address:%p, array2 address:%p", array1, array2);
NSLog(@"array1:%@, array2:%@", array1, array2);
打印结果如下:
2021-01-12 14:15:59.713219+0800 ShallowCopy&DeepCopy[49942:28050025] array2为可变数组
2021-01-12 14:15:59.713366+0800 ShallowCopy&DeepCopy[49942:28050025] array1 address:0x600003d49ce0, array2 address:0x600003d49c80
2021-01-12 14:15:59.713559+0800 ShallowCopy&DeepCopy[49942:28050025] array1:(
"<Animal: 0x600003d49d40>"
), array2:(
"<Animal: 0x600003d49d40>"
)
结果发现与上面的可变字符串的结果相同:数组本身进行了深拷贝,但内部的对象只进行了浅拷贝。
单层拷贝
对于上面的两种情况来说,数组的深拷贝并非严格意义上的深拷贝,只能算是单层深拷贝,虽然数组新开辟了内存空间,但是数组里存放的元素仍然是之前数组元素的值,并没有另外复制一份。
那么如何才能真正的实现深拷贝呢?
方法一:遍历数组,对每个元素深拷贝
Animal *animal1 = [[Animal alloc] init];
animal1.age = @"1";
animal1.name = @"animal1";
animal1.sex = 1;
Animal *animal2 = [[Animal alloc] init];
animal2.age = @"2";
animal2.name = @"animal2";
animal2.sex = 2;
NSMutableArray *array1 = [NSMutableArray arrayWithObjects:animal1, animal2, nil];
NSMutableArray *array2 = [NSMutableArray arrayWithCapacity:0];
for (Animal *animal in array1) {
[array2 addObject:[animal mutableCopy]];
}
NSLog(@"array1 address:%p, array2 address:%p", array1, array2);
NSLog(@"array1:%@, array2:%@", array1, array2);
打印结果如下:
2021-01-12 14:34:53.842362+0800 ShallowCopy&DeepCopy[58333:28097043] array1 address:0x600002f3a790, array2 address:0x600002f3a250
2021-01-12 14:34:53.842640+0800 ShallowCopy&DeepCopy[58333:28097043] array1:(
"<Animal: 0x600002f3a8e0>",
"<Animal: 0x600002f3a850>"
), array2:(
"<Animal: 0x600002f3a670>",
"<Animal: 0x600002f3a190>"
)
从打印结果可以看出,array2进行了深拷贝,array2内部的元素也进行了深拷贝。
方法二:initWithArray:copyItems
但是这种方法需要对元素进行遍历,不是很友好。其实官方已经提供了另外的一种方式initWithArray:copyItems
Animal *animal1 = [[Animal alloc] init];
animal1.age = @"1";
animal1.name = @"animal1";
animal1.sex = 1;
Animal *animal2 = [[Animal alloc] init];
animal2.age = @"2";
animal2.name = @"animal2";
animal2.sex = 2;
NSMutableArray *array1 = [NSMutableArray arrayWithObjects:animal1, animal2, nil];
id array2 = [[NSMutableArray alloc] initWithArray:array1 copyItems:YES];
NSLog(@"array1 address:%p, array2 address:%p", array1, array2);
NSLog(@"array1:%@, array2:%@", array1, array2);
打印结果如下:
2021-01-12 14:41:17.060980+0800 ShallowCopy&DeepCopy[61190:28113308] array1 address:0x600000aa0090, array2 address:0x600000aa1020
2021-01-12 14:41:17.061229+0800 ShallowCopy&DeepCopy[61190:28113308] array1:(
"<Animal: 0x600000aa0c30>",
"<Animal: 0x600000aa00c0>"
), array2:(
"<Animal: 0x600000aa0c90>",
"<Animal: 0x600000aa0030>"
)
从打印结果来看和我们遍历得到的结果是一致的。
我们在这个基础上再增加一层模型对象试一下
Animal *animal1 = [[Animal alloc] init];
animal1.age = @"1";
animal1.name = @"animal1";
animal1.sex = 1;
animal1.dog = [[Dog alloc] init];
animal1.dog.age = @"1";
animal1.dog.name = @"dog1";
animal1.dog.sex = 1;
NSMutableArray *array1 = [NSMutableArray arrayWithObjects:animal1, nil];
id array2 = [[NSMutableArray alloc] initWithArray:array1 copyItems:YES];
NSLog(@"array1 address:%p, array2 address:%p", array1, array2);
for (Animal *animal in array1) {
NSLog(@"animal:%p. dog:%p", animal, animal.dog);
}
NSLog(@"==========");
for (Animal *animal in array2) {
NSLog(@"animal:%p. dog:%p", animal, animal.dog);
}
打印结果如下:
2021-01-12 14:57:59.161465+0800 ShallowCopy&DeepCopy[68732:28155896] array1 address:0x6000003e1fe0, array2 address:0x6000003e1c80
2021-01-12 14:57:59.161668+0800 ShallowCopy&DeepCopy[68732:28155896] animal:0x6000003e1f20. dog:0x600000dba960
2021-01-12 14:57:59.161805+0800 ShallowCopy&DeepCopy[68732:28155896] ==========
2021-01-12 14:57:59.161925+0800 ShallowCopy&DeepCopy[68732:28155896] animal:0x6000003e2010. dog:0x600000dba980
我们在这个基础上再增加一层数组试一下
Animal *animal1 = [[Animal alloc] init];
animal1.age = @"1";
animal1.name = @"animal1";
animal1.sex = 1;
Dog *dog1 = [[Dog alloc] init];
dog1.age = @"1";
dog1.name = @"dog1";
dog1.sex = 1;
animal1.modelArray = [[NSMutableArray alloc] init];
[animal1.modelArray addObject:dog1];
NSMutableArray *array1 = [NSMutableArray arrayWithObjects:animal1, nil];
id array2 = [[NSMutableArray alloc] initWithArray:array1 copyItems:YES];
NSLog(@"array1 address:%p, array2 address:%p", array1, array2);
for (Animal *animal in array1) {
NSLog(@"animal:%p. dog:%p", animal, animal.modelArray);
}
NSLog(@"==========");
for (Animal *animal in array2) {
NSLog(@"animal:%p. dog:%p", animal, animal.modelArray);
}
打印结果如下:
2021-01-12 14:54:08.419263+0800 ShallowCopy&DeepCopy[66975:28145985] array1 address:0x600001f8a0a0, array2 address:0x600001f89650
2021-01-12 14:54:08.419429+0800 ShallowCopy&DeepCopy[66975:28145985] animal:0x600001f89da0. dog:0x600001f89fb0
2021-01-12 14:54:08.419263+0800 ShallowCopy&DeepCopy[66975:28145985] array1 address:0x600001f8a0a0, array2 address:0x600001f89650
2021-01-12 14:54:08.419429+0800 ShallowCopy&DeepCopy[66975:28145985] animal:0x600001f89da0. dog:0x600001f89fb0
2021-01-12 14:54:08.419553+0800 ShallowCopy&DeepCopy[66975:28145985] ==========
2021-01-12 14:54:08.419674+0800 ShallowCopy&DeepCopy[66975:28145985] animal:0x600001f89a10. dog:0x6000013f4cb0
通过结果可以发现模型中的数组不会开辟新的内存空间,仍然是同一个对象。
所以使用initWithArray:copyItems时模型本身的属性都会进行深拷贝,但如果模型属性还包含数组,那这个方法就不管用了。
方法三:归档解档
还有另外一种方式,就是归档解档
Animal *animal1 = [[Animal alloc] init];
animal1.age = @"1";
animal1.name = @"animal1";
animal1.sex = 1;
Dog *dog1 = [[Dog alloc] init];
dog1.age = @"1";
dog1.name = @"dog1";
dog1.sex = 1;
animal1.modelArray = [[NSMutableArray alloc] init];
[animal1.modelArray addObject:dog1];
NSMutableArray *array1 = [NSMutableArray arrayWithObjects:animal1, nil];
NSString *filePath = [NSTemporaryDirectory() stringByAppendingPathComponent:@"t.data"];
NSData *data = [NSKeyedArchiver archivedDataWithRootObject:array1 requiringSecureCoding:YES error:nil];
[data writeToFile:filePath atomically:YES];
NSData *data1 = [NSData dataWithContentsOfFile:filePath];
NSSet *classSet = [NSSet setWithObjects:[NSMutableArray class], [Animal class], [Dog class], nil];
NSError *error;
NSMutableArray *array2 = [NSKeyedUnarchiver unarchivedObjectOfClasses:classSet fromData:data1 error:&error];
NSLog(@"array1 address:%p, array2 address:%p", array1, array2);
for (Animal *animal in array1) {
NSLog(@"animal:%p. modelArray:%p", animal, animal.modelArray);
}
NSLog(@"==========");
for (Animal *animal in array2) {
NSLog(@"animal:%p. modelArray:%p", animal, animal.modelArray);
}
打印结果如下:
2021-01-12 15:10:39.873687+0800 ShallowCopy&DeepCopy[74371:28189240] array1 address:0x6000032ff810, array2 address:0x6000032e1e90
2021-01-12 15:10:39.873830+0800 ShallowCopy&DeepCopy[74371:28189240] animal:0x6000032fefd0. modelArray:0x6000032ff720
2021-01-12 15:10:39.873938+0800 ShallowCopy&DeepCopy[74371:28189240] ==========
2021-01-12 15:10:39.874059+0800 ShallowCopy&DeepCopy[74371:28189240] animal:0x6000032f2940. modelArray:0x6000032e1ec0
从打印结果可以看出模型中的数组也进行了深拷贝,其实也很好理解,归档解档的原理就是将内存中的数据写入本地文件里,当再从本地文件中读出数据时肯定是要新开辟出内存空间的。 如果模型嵌套层次较深时,可以使用归档解档进行深拷贝。
注意一
使用对象的mutableCopy和数组使用initWithArray:copyItems时需要模型遵守NSCopying或NSMutableCopying协议并重写以下方法
- (id)copyWithZone:(NSZone *)zone {
Animal *copy = [[Animal allocWithZone:zone] init];
copy.name = [_name copyWithZone:zone];
copy.age = [_age copyWithZone:zone];
copy.dog = [_dog copyWithZone:zone];
copy.modelArray = [_modelArray copyWithZone:zone];
return copy;
}
- (id)mutableCopyWithZone:(NSZone *)zone {
Animal *copy = [[Animal allocWithZone:zone] init];
copy.name = [_name mutableCopyWithZone:zone];
copy.age = [_age mutableCopyWithZone:zone];
copy.dog = [_dog mutableCopyWithZone:zone];
copy.modelArray = [_modelArray copyWithZone:zone];
return copy;
}
注意二
使用归档解档时需要遵守NSSecureCoding协议并重写以下方法
+ (BOOL)supportsSecureCoding {
return YES;
}
- (id)initWithCoder:(NSCoder *)coder {
if (self = [super init]) {
self.name = [coder decodeObjectForKey:@"name"];
self.age = [coder decodeObjectForKey:@"age"];
self.sex = [coder decodeIntForKey:@"sex"];
self.dog = [coder decodeObjectForKey:@"dog"];
self.modelArray = [coder decodeObjectForKey:@"modelArray"];
}
return self;
}
- (void)encodeWithCoder:(NSCoder *)coder {
[coder encodeObject:self.name forKey:@"name"];
[coder encodeObject:self.age forKey:@"age"];
[coder encodeInt:self.sex forKey:@"sex"];
[coder encodeObject:self.dog forKey:@"dog"];
[coder encodeObject:self.modelArray forKey:@"modelArray"];
}