这里主要有两个概念深拷贝和浅拷贝,主要涉及两个方法 - (id)copy;
、- (id)mutableCopy;
和一个属性修饰关键字@copy。
深拷贝 :即内容拷贝,指拷贝对象的具体内容,而内存地址是重新分配的,两个对象虽然存的值是相同的,但是内存地址不一样,两个对象也互不影响,互不干涉(对于集合类这里有点特殊,下面会详细讲述)。
浅拷贝:即指针拷贝,是对内存地址的复制,让目标对象指针和源对象指向同一片内存空间。
一、对象可以实现拷贝的前提
在OC中,若要使对象可以拥有可拷贝操作,则该对象所属的类必须遵守NSCopying
或NSMutableCopy
协议,并重写copyWithZone:
或mutableCopyWithZone:
方法。
对于自定义类一般只实现NSCopying协议,同时如果遵循了NSCopying协议同时实现了copyWithZone:
方法,则实例对象调用copy方法([animal copy])时会生成一个新的对象,值得注意的是这是一个深拷贝。
- 若自定义类直接继承于NSObject类,则不需要[super copyWithZone:zone] ;
- 若自定义类的父类或者超类继承与NSObject,且其父类或超类均未遵循NSCopying协议,则不需要[super copyWithZone:zone] ;
- 若自定义类的父类或者超类继承与NSObject,且其父类或超类遵循NSCopying协议,则需要[super copyWithZone:zone] ;
@interface Animal : NSObject
@property (nonatomic,copy) NSString * name;
@property (nonatomic,copy) NSString * age;
@end
@interface Animal()<NSCopying>
@end
// --------------------------------------------------------
@implementation Animal
- (id)copyWithZone:(NSZone *)zone
{
Animal * animal = [[Animal allocWithZone:zone] init];
animal.name = self.name;
animal.age = self.age;
return animal;
}
// -----------------------------------------------------
//在其他地方调用
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
Animal * animal = [[Animal alloc] init];
animal.name = @"男儿何惜死";
animal.age = @"27";
Animal * copyAnimal = [animal copy];
NSLog(@"animal.name/age = %@/%@, copyAnimal.name/age = %@/%@", animal.name, animal.age, copyAnimal.name, copyAnimal.age);
NSLog(@"animal.address = %p, copyAnimal.address = %p", animal, copyAnimal);
}
@end
二、Copy和MutableCopy的区别
总的来说:如果不可变对象进行复制,copy是指针复制(浅拷贝)和mutableCopy就是对象复制(深拷贝)。如果是对可变对象复制,都是深拷贝。Copy 返回一个不可变对象的副本,MutalbeCopy返回一个可变对象的副本。至于深拷贝到什么地步,下面针对不同情况一一列举:
1、NSString、NSMutableString:
- 普通字符串NSString/NSMutableString使用copy与mutableCopy的区别:
NSString * string = @"男儿何惜死";
NSString * copyString = [string copy];
NSMutableString * mutableCopyStr = [string mutableCopy];
NSLog(@"string = %p, copyStr = %p mutableCopyStr = %p", string, copyString, mutableCopyStr);
NSMutableString * mutableString = [NSMutableString stringWithString:@"破胆与君尝"];
NSString * copyMutableStr = [mutableString copy];
NSMutableString * mutableCopyMutStr = [mutableString mutableCopy];
NSLog(@"mutableString = %p, copyMutableStr = %p mutableCopyMutStr = %p", mutableString, copyMutableStr, mutableCopyMutStr);
// ------------------------------------------------
// 打印结果:验证了总结的结论
string = 0x10e1cf098, copyStr = 0x10e1cf098 mutableCopyStr = 0x600000442520
mutableString = 0x600000442550, copyMutableStr = 0x600000442580 mutableCopyMutStr = 0x6000004425b0
- 对于NSString类来说当作为属性时,关键字@copy与@strong的区别:
- 当赋予属性值为不可变字符串时,使用@copy和@strong关键字是没有区别的;
- 当赋予属性值为可变字符串时,需要特别谨慎使用@copy和@strong;因为使用@strong修饰后属性指针与源指针指向同一块内存区域,当该内存中数据发生变化属性值也会变化,这可能不是我们想要看到的。
@property (nonatomic,copy) NSString * name;
@property (nonatomic,strong) NSString * strongName;
// ----------------------------------------------------
NSMutableString * mutableString = [NSMutableString stringWithString:@"男儿何惜死"];
self.name = mutableString;
self.strongName = mutableString;
NSLog(@"mutableString = %p, self.name = %p self.strongName = %p", mutableString, self.name, self.strongName);
NSLog(@"mutableString = %@, self.name = %@ self.strongName = %@", mutableString, self.name, self.strongName);
[mutableString appendString:@",破胆与君尝"];
NSLog(@"mutableString = %@, self.name = %@ self.strongName = %@", mutableString, self.name, self.strongName);
// ----------------------------------------------------
mutableString = 0x600000241a40, self.name = 0x600000241a70 self.strongName = 0x600000241a40
mutableString = 男儿何惜死, self.name = 男儿何惜死 self.strongName = 男儿何惜死
mutableString = 男儿何惜死,破胆与君尝, self.name = 男儿何惜死 self.strongName = 男儿何惜死,破胆与君尝
2、NSArray/NSMutableArray、NSDictionary/NSMutableDictionary、NSSet/NSMutableSet等集合类
(1、)对于常见使用@strong修饰的不可变集合:
@property (nonatomic,strong) NSArray * testArray;
// -------------------------------------------------------
// 示例一:赋予全部是不可变元素的不可变集合
self.testArray = @[@"不负光阴", @"不负卿"];
NSArray * test_1 = [self.testArray copy];
NSMutableArray * test_2 = [self.testArray mutableCopy];
NSLog(@"self.testArray = %p, test_1 = %p, test_2 = %p", self.testArray, test_1, test_2);
NSLog(@"\n self.testArray.firstObject = %p, self.testArray.lastObject = %p;
\n test_1.firstObject = %p, test_1.lastObject = %p;\n test_2.firstObject = %p, test_2.lastObject = %p",
self.testArray[0], self.testArray[1], test_1[0], test_1[1], test_2[0], test_2[1]);
// ------------------------------------------------------
self.testArray = 0x604000039f60, test_1 = 0x604000039f60, test_2 = 0x60400025d130
self.testArray.firstObject = 0x10f61d0a8, self.testArray.lastObject = 0x10f61d0c8;
test_1.firstObject = 0x10f61d0a8, test_1.lastObject = 0x10f61d0c8;
test_2.firstObject = 0x10f61d0a8, test_2.lastObject = 0x10f61d0c8
// ---------------------------------------------------------
// 由上可知,虽然mutableCopy是深拷贝,但是不彻底,test_2中的元素与源数组元素依然指向同一块内存区域。
// 这在数组元素存在可变元素时会发生奇妙的事情。
// 示例二:赋予含有可变元素的不可变集合
self.testArray = @[[NSMutableString stringWithString:@"不负光阴"], @"不负卿"];
NSArray * test_1 = [self.testArray copy];
NSMutableArray * test_2 = [self.testArray mutableCopy];
NSLog(@"self.testArray = %@, test_1 = %@, test_2 = %@", self.testArray, test_1, test_2);
NSLog(@"self.testArray = %p, test_1 = %p, test_2 = %p", self.testArray, test_1, test_2);
NSLog(@"\n self.testArray.firstObject = %p, self.testArray.lastObject = %p;
\n test_1.firstObject = %p, test_1.lastObject = %p;\n test_2.firstObject = %p, test_2.lastObject = %p",
self.testArray[0], self.testArray[1], test_1[0], test_1[1], test_2[0], test_2[1]);
NSMutableString * firstStr = [self.testArray firstObject];
[firstStr appendString:@",不负自己"];
NSLog(@"self.testArray = %@, test_1 = %@, test_2 = %@", self.testArray, test_1, test_2);
// --------------------------------------------------------
self.testArray = (
"不负光阴",
"不负卿",
),
test_1 = (
"不负光阴",
"不负卿",
),
test_2 = (
"不负光阴",
"不负卿",
)
self.testArray = 0x60400022bf80, test_1 = 0x60400022bf80, test_2 = 0x604000445100
self.testArray.firstObject = 0x604000442430, self.testArray.lastObject = 0x1030c8df8;
test_1.firstObject = 0x604000442430, test_1.lastObject = 0x1030c8df8;
test_2.firstObject = 0x604000442430, test_2.lastObject = 0x1030c8df8
self.testArray = (
"不负光阴,不负自己",
"不负卿",
),
test_1 = (
"不负光阴,不负自己",
"不负卿",
),
test_2 = (
"不负光阴,不负自己",
"不负卿",
)
// --------------------------------------------------------
// 可以看到所有数组的第一个元素全部发生了改变。同样数组内即使存在可变数元素,外部使用mutableCopy,内部元素依然指向同一块内存区域!
// 试想一下如果 将[firstStr appendString:@",不负自己"];
// 改为 firstStr = [NSMutableString stringWithString:@"不负光阴,不负自己"];
//打印结果如何?为什么?
// 示例三:赋予可变集合(这里与NSString相似)
NSMutableArray * baseArray = [NSMutableArray arrayWithArray:@[@"不负光阴", @"不负卿"]];
self.testArray = baseArray;
NSArray * test_1 = [self.testArray copy];
NSMutableArray * test_2 = [self.testArray mutableCopy];
NSLog(@"baseArray = %p, self.testArray = %p, test_1 = %p, test_2 = %p", baseArray, self.testArray, test_1, test_2);
NSLog(@"baseArray = %@, self.testArray = %@", baseArray, self.testArray);
[baseArray addObject:@"不负自己"];
NSLog(@"baseArray = %@, self.testArray = %@", baseArray, self.testArray);
// ----------------------------------------------------------
baseArray = 0x6000004442f0, self.testArray = 0x6000004442f0, test_1 = 0x6000000342e0, test_2 = 0x600000444320
baseArray = (
"不负光阴",
"不负卿",
), self.testArray = (
"不负光阴",
"不负卿",
)
baseArray = (
"不负光阴",
"不负卿",
"不负自己",
), self.testArray = (
"不负光阴",
"不负卿",
"不负自己",
)
// ----------------------------------------------------------
// 赋值操作是一个浅拷贝的过程;
// 可以看到当源数据发生变化时self.testArray也与之变化,这可能也不是我们想看到到~
(2、)使用@copy关键字修饰的不可变集合:
当赋予其不可变集合对象时与@strong没有区别,对其进行copy或者mutableCopy操作与(1、)中示例一、二情况一致,即copy操作生成的对象与原对象指向同一块内存区域,mutableCopy则不同;
当对其赋予可变集合时,由于是@copy修饰,此时虽然是赋值操作同时也是一个深拷贝的过程,会在一个新的内存地址来存储@copy修饰的集合,集合内元素的地址与源集合中元素地址依然一样,但是此时再修改源集合,目标集合不会再改变!
@property (nonatomic,copy) NSArray * testCopyArray;
// -------------------------------------------------------------------
NSMutableArray * baseArray = [NSMutableArray arrayWithArray:@[@"不负光阴", @"不负卿"]];
self.testCopyArray = baseArray;
NSArray * test_1 = [self.testCopyArray copy];
NSMutableArray * test_2 = [self.testCopyArray mutableCopy];
NSLog(@"baseArray = %p, self.testCopyArray = %p, test_1 = %p, test_2 = %p", baseArray, self.testCopyArray, test_1, test_2);
NSLog(@"baseArray = %@, self.testCopyArray = %@", baseArray, self.testCopyArray);
[baseArray addObject:@"不负自己"];
NSLog(@"baseArray = %@, self.testCopyArray = %@", baseArray, self.testCopyArray);
// -------------------------------------------------------------------
baseArray = 0x60400025de80, self.testCopyArray = 0x60400022bb60, test_1 = 0x60400022bb60, test_2 = 0x60400025deb0
baseArray = (
"不负光阴",
"不负卿",
), self.testCopyArray = (
"不负光阴",
"不负卿",
)
baseArray = (
"不负光阴",
"不负卿",
"不负自己",
), self.testCopyArray = (
"不负光阴",
"不负卿",
)
// -------------------------------------------------------------------
// 是不是觉得使用@copy修饰的不可变集合更靠谱一些😂
// 同时需要注意的是:对于不可变数组,不管用什么修饰,不管给予其赋值的是可变还是不可变的数组,copy操作生成的对象与原对象均指向同一块内存区域!!!
(3、)使用@strong修饰的可变集合:
// 与(1、)中的第三种情况(示例三)相似
@property (nonatomic,strong) NSMutableArray * testArray;
// ---------------------------------------------------------------
NSMutableArray * baseArray = [NSMutableArray arrayWithArray:@[@"不负光阴", @"不负卿"]];
self.testArray = baseArray;
NSArray * test_1 = [self.testArray copy];
NSMutableArray * test_2 = [self.testArray mutableCopy];
NSLog(@"baseArray = %p, self.testArray = %p, test_1 = %p, test_2 = %p", baseArray, self.testArray, test_1, test_2);
NSLog(@"baseArray = %@, self.testArray = %@", baseArray, self.testArray);
[baseArray addObject:@"不负自己"];
NSLog(@"baseArray = %@, self.testArray = %@", baseArray, self.testArray);
// -------------------------------------------------------------------
baseArray = 0x6000004496c0, self.testArray = 0x6000004496c0, test_1 = 0x6000000307c0, test_2 = 0x6000004496f0
baseArray = (
"不负光阴",
"不负卿",
), self.testArray = (
"不负光阴",
"不负卿",
)
baseArray = (
"不负光阴",
"不负卿",
"不负自己",
), self.testArray = (
"不负光阴",
"不负卿",
"不负自己",
)
(4、)使用@copy修饰的可变集合:
// 为其赋值等操作均没啥问题,问题出在将其作为一个值,赋予其他可变集合时!
@property (nonatomic,copy) NSMutableArray * testCopyArray;
// -------------------------------------------------------------------
self.testCopyArray = [NSMutableArray arrayWithArray:@[@"不负光阴", @"不负卿"]];
NSMutableArray * baseArray = [NSMutableArray array];
baseArray = self.testCopyArray;
NSLog(@"baseArray = %p, self.testCopyArray = %p", baseArray, self.testCopyArray);
[self.testCopyArray addObject:@"不负自己"];
[baseArray addObject:@"不负自己"];
// -------------------------------------------------------------------
//此时会崩溃
baseArray = 0x60400022acc0, self.testCopyArray = 0x60400022acc0
-[__NSArrayI addObject:]: unrecognized selector sent to instance 0x60400022acc0
// 可以看到baseArray已经是一个不可变的集合,所以对其进行增删等操作会引发异常。
// 原因就是因为修饰词是@copy,我们上面已经强调了copy操作返回不可变对象,
// mutableCopy返回可变对象,这与是否是深拷贝与浅拷贝无关,要切记。
以上均是用数组举例,其实字典等也相似的,可以自行尝试一下。这里给出一个简单的🌰
@property (nonatomic,strong) NSDictionary * testDic;
// ---------------------------------------------------------------------
NSMutableDictionary * dic = [NSMutableDictionary dictionaryWithDictionary:@{@"001" : @"男儿何惜死", @"002" : @"破胆与君尝"}];
self.testDic = dic;
NSDictionary * test_1 = [self.testDic copy];
NSMutableDictionary * test_2 = [self.testDic mutableCopy];
NSLog(@"dic = %p, self.testDic = %p, test_1 = %p, test_2 = %p", dic, self.testDic, test_1, test_2);
NSLog(@"dic = %@, self.testDic = %@", dic, self.testDic);
[dic setObject:@"两情若是久长时" forKey:@"003"];
NSLog(@"dic = %@, self.testDic = %@", dic, self.testDic);
// ---------------------------------------------------------------------
dic = 0x604000030320, self.testDic = 0x604000030320, test_1 = 0x604000030340, test_2 = 0x604000030360
dic = {
001 = "男儿何惜死",
002 = "破胆与君尝",
}, self.testDic = {
001 = "男儿何惜死",
002 = "破胆与君尝",
}
dic = {
001 = "男儿何惜死",
002 = "破胆与君尝",
003 = "两情若是久长时",
}, self.testDic = {
001 = "男儿何惜死",
002 = "破胆与君尝",
003 = "两情若是久长时",
}
三、集合的真正的深拷贝
上面提到集合的深拷贝并不彻底,集合内的元素的地址依然是相同的,那么如何实现集合的真正的拷贝呢?参考文档
// 可以将该集合归档然后再解档,前提是内容均符合NSCoding协议,这样可以得到一个真正意义上的深拷贝。
@property (nonatomic,strong) NSArray * testArray;
// ---------------------------------------------------------------------
self.testArray = @[@"不负光阴", @"不负卿"];
NSArray * deepArray = [NSKeyedUnarchiver unarchiveObjectWithData:[NSKeyedArchiver archivedDataWithRootObject:self.testArray]];
NSLog(@"self.testArray = %p, deepArray = %p", self.testArray, deepArray);
NSLog(@"self.testArray = %@, deepArray = %@", self.testArray, deepArray);
NSLog(@"self.testArray[0] = %p, self.testArray[1] = %p, deepArray[0] = %p, deepArray[1] = %p", self.testArray[0], self.testArray[1], deepArray[0], deepArray[1]);
// ---------------------------------------------------------------------
self.testArray = 0x604000229b60, deepArray = 0x604000229be0
self.testArray = (
"不负光阴",
"不负卿",
), deepArray = (
"不负光阴",
"不负卿",
)
self.testArray[0] = 0x10350d238, self.testArray[1] = 0x10350d258, deepArray[0] = 0x604000229c20, deepArray[1] = 0x604000229b80