预备知识
内存的栈区:由编译器自动分配释放存放函数的参数值,局部变量的值等,其操作方式类似于数据结构中的栈
内存的堆区:一般由程序员分配释放,若程序员不释放,程序结束时可能由OS回收,主要以他与数据结构中的堆是两码事,分配方式倒是类似于链表
关于浅拷贝,简单来说,就像是人与人的影子一样,也可以说是指针拷贝,而深拷贝就像是我们人类,虽然都是被统一称为人,但是人与人之间是相互独立的,也可以说是内容拷贝,会产生一个新的对象,新的指针。
通常我们都有这样的误解,任务浅拷贝就是用copy,深拷贝就是用mutableCopy。这种理解是错误的,一定要更正过来。copy只是不可变拷贝,而mutableCopy是可变拷贝。比如,NSArray *arr = [modelsArray copy],那么arr是不可变的,而NSMutableArray *ma = [modelsArray mutableCopy],那么ma是可变的。
/*系统对象的copy与mutableCopy方法
不管是集合类对象(数组,字典,set等)还是非集合类对象,接收到copy和mutableCopy消息时,都遵循一下准则
1.copy返回的都是imutable(不可变)对象,所以如果对copy返回值使用mutable对象接口时就会crash
2.mutablecopy返回mutable(可变)对象
3.在非集合类对象中,对immutable对象进行copy操作,是指针复制,mutablecopy操作是内容复制,对mutable对象不管是进行copy还是mutablecopy操作是都是内容复制
[immutableObject copy] 浅复制
[immutableObject mutableCopy] 深复制
[mutableObject copy] 深复制
[mutableObject mutableCopy] 深复制
4. 在集合类对象中,对immutable对象进行copy操作时,是指针复制,mutableCopy是内容复制,对mutable对象进行copy和mutableCopy都是内容复制。但是集合对象的内容复制仅限于对象本身,对象元素的指针仍然是指针复制,也就是说不管是源头对象还是复制对象,其中某一个对象元素的值发生改变,另外一个也会跟着改变
[immutableObject copy] 浅复制
[immutableObejct mutableCopy] 单层深复制
[mutableObject copy] //单层深复制
[mutableObejct mutableCopy]//单层深复制
*/
5.默认状况下,深拷贝指的都是不完全深拷贝,如果要实现完全深拷贝,则要重写copyWithZone:方法,自行实现完全深拷贝的需求,大体思路如下,再copyWithZone:里对象赋值不直接赋值而是通过copy方法即可实现。
我们来通过代码来验证一下
我们先来看看集合类对象
//可变数组
NSMutableArray *modelArr = [NSMutableArray arrayWithObjects:@"hello",@"world", nil];;
NSMutableArray *copyArr = [modelArr copy];
[copyArr addObject:@"!"];//运行到这里会闪退,应该返回的是不可变对象,而我们调用了可变对象的方法addObject
NSMutableArray *modelArr = [NSMutableArray arrayWithObjects:@"hello",@"world", nil];
NSMutableArray *mutableArr = [modelArr mutableCopy];
[mutableArr addObject:@"!"];
NSLog(@"%@",mutableArr);
NSLog(@"%@",modelArr);
//不可变数组
NSArray *arr = @[@"hello",@"world"];
NSMutableArray *mutableArr = [arr mutableCopy];
[mutableArr addObject:@"!"];
NSLog(@"%@",mutableArr);
非集合类对象
//可变字符串
NSMutableString *mutableStr = [NSMutableString stringWithFormat:@"hello world"];
NSMutableString *copyStr= [mutableStr copy];
//[copyStr appendString:@"!"];//这里会闪退,因为是copy操作,返回的是不可变对象,即使他用的是NSMutableString类型做前缀
NSMutableString *mutableCopyStr = [mutableStr mutableCopy];
[mutableCopyStr appendString:@"!"];
//不可变字符串
NSString *str = @"hello world";
NSMutableString *copyStr= [str copy];
[copyStr appendString:@"!"];//这里会闪退,因为是copy操作,返回的是不可变对象,即使他用的是NSMutableString类型做前缀
NSMutableString *mutableCopyStr = [str mutableCopy];
[mutableCopyStr appendString:@"!"];
再来看看在非集合类对象中,对immutable对象进行copy或者mutableCopy操作,是指针复制还是是内容复制,对mutable对象不管是进行copy还是mutablecopy操作是都是内容复制
在写代码的过程中出现了一个让我很疑惑的问题,看下面代码,既然说immutable对象进行copy是指针复制,但是我对copyStr重新赋值或者设置nil,那么原有的字符串是不是也应该改变,但是打印出来的结果完全不是我想的那样!通过下面的打印结果我们可以分析出,在对字符串做copy操作时,原变量的值所指向的内存地址和copy变量是一样的,但是他们两个变量自身的内存地址是不一样的,再对copy重新赋值之后,发现变量自身的内存地址没有变化,原变量的值所只想的内存地址没有变化,copy变量的值所指向的内存地址变化了,是不是很奇怪,这不是赋值吗,为什么内存地址会变化了,其实可以这样理解,在做赋值过程中,我们是把一个新的字符串对象的地址赋值给了copy变量,所以变量值的内存地址才会发生变化。
//不可变字符串
NSString *str = @"hello world";
NSString *copyStr= [str copy];
NSMutableString *mutableCopyStr = [str mutableCopy];
// [mutableCopyStr appendString:@"!"];
//copyStr = @"hello china";
// NSLog(@"%@====%@",str,copyStr);
NSLog(@"%p-------%p-------%p",str,copyStr,mutableCopyStr);//打印的地址是变量的 值 的内存地址,即对值的引用,内存地址指向@"hello world"这个对象
NSLog(@"%p-------%p-------%p",&str,©Str,&mutableCopyStr);//打印的地址是对象本身的内存地址
copyStr = @"hello China";//这个时候应该是变量本身的内存地址没有变化,而变量的值的内存地址应该由指向@“hello world”变成指向了@"hello China"
NSLog(@"%p-------%p-------%p",str,copyStr,mutableCopyStr);//打印的地址是变量的 值 的内存地址,即对值的引用,内存地址指向@"hello world"这个对象
NSLog(@"%p-------%p-------%p",&str,©Str,&mutableCopyStr);//打印的地址是对象本身的内存地址
NSMutableString *str = [NSMutableString stringWithFormat:@"hello world"];
NSString *copyStr= [str copy];
NSMutableString *mutableCopyStr = [str mutableCopy];
copyStr = @"hello China";
mutableCopyStr = @"hello Apple";
NSLog(@"%@====%@====%@",str,copyStr,mutableCopyStr);
//NSLog(@"%@====%@",str,copyStr);
NSLog(@"%p-------%p-------%p",str,copyStr,mutableCopyStr);//打印的地址是变量的 值 的内存地址,即对值的引用,内存地址指向@"hello world"这个对象
NSLog(@"%p-------%p-------%p",&str,©Str,&mutableCopyStr);//打印的地址是对象本身的内存地址
很容易看出mutable对象不管是copy还是mutableCopy操作,复制的对象的内存地址与原对象完全不一致,重新赋值也互相不影响,所以说是内容复制
我们再来看看在集合类对象中,对immutable对象进行copy或者mutableCopy操作时,是指针复制还是是内容复制
//不可变数组
NSArray *arr = @[@"hello",@"world"];
NSMutableArray *mutableArr = [arr mutableCopy];
NSArray *copyArr = [arr copy];
//打印对象内容的地址
NSLog(@"%@=====%@",arr,copyArr);
NSLog(@"%p",arr);
NSLog(@"%p",mutableArr);
NSLog(@"%p",copyArr);
arr = nil;
NSLog(@"%@=====%@",arr,copyArr);
Person *p = [[Person alloc] init];
Person *p1 = [[Person alloc] init];
NSArray *pArr = @[p,p1];
NSArray *pCopyArr = [pArr copy];
NSLog(@"%@=====%@",pArr,pCopyArr);
pArr = nil;
NSLog(@"%@=====%@",pArr,pCopyArr);
很明显我们可以看到arr和copyArr内存地址相同,mutableArr是和arr相互独立的
我们也可以看到我们把原数组设为nil,对copy数组没有影响,原因是集合类imutable对象进行copy操作是是指针复制,引用计数+1
//数组元素是字符串
NSMutableArray *modelsArray = [NSMutableArray arrayWithArray:@[@"123",@"456"]];
//arr是不可变的
// NSArray *arr = [modelsArray copy];
NSMutableArray *arr = [modelsArray mutableCopy];
arr[0] = @"jse3";
NSLog(@"modelsArr =%@ ma=%@ ",modelsArray[0],arr[0]);
//ma是可变的
NSMutableArray *ma = [modelsArray mutableCopy];
NSMutableArray *otherMa = [modelsArray copy];//因为用的是copy,返回的都应该是不可变对象,所以other其实是NSArray类型,如果修改元素的值会crash
ma[0] = @"789";
modelsArray[1] = @"hhaha";
NSLog(@"%@",[otherMa class]);
NSLog(@"modelsArr =%@ ma=%@ otherma=%@",modelsArray[1],ma[1],otherMa[1]);//打印结果显示,不管是哪个数组元素值发生变化,其他的数组的元素值都没有变化
//数组元素是对象
NSMutableArray *personArr = [[NSMutableArray alloc] init];
BRPerson *person1 = [[BRPerson alloc] init];
person1.name = @"lili";
[personArr addObject:person1];
BRPerson *person2 = [[BRPerson alloc] init];
person2.name = @"lisa";
[personArr addObject:person2];
//浅拷贝
NSArray *newArr = [personArr copy];
BRPerson *p = newArr[0];
p.name = @"lili的名字被修改了";
NSLog(@"%@",((BRPerson *)personArr[0]).name);//打印结果为lili的名字被修改了,表示原来的数组中的元素value也修改了
NSMutableArray *arr2 = [personArr mutableCopy];
[arr2 removeObjectAtIndex:0];
BRPerson *p2 = arr2[0];
p2.name = @"sdsdsasdd";
NSLog(@"%@ ---------- %@",((BRPerson *)newArr[0]).name,((BRPerson *)personArr[0]).name);//打印结果为lili的名字被修改了,表示原来的数组中的元素value也修改了
通过上面的代码我们就知道了集合对象的内容复制仅限于对象本身,对象元素的指针仍然是指针复制,也就是说不管是源头对象还是复制对象,其中某一个对象元素的值发生改变,另外一个也会跟着改变,当然你也可以通过打印源头对象和复制对象的对象元素的地址,来看一下他们是否相同。
自定义类支持copy和mutableCopy
直接看代码
// .h
@interface BRTest : NSObject
@property (nonatomic,copy) NSString *name;
@end
// .m
@interface BRTest()
@end
@implementation BRTest
- (id)copyWithZone:(NSZone *)zone{
//浅拷贝
BRTest *test = self;
test.name = [self.name copy];
return test;
}
- (id)mutableCopyWithZone:(NSZone *)zone{
//深拷贝
BRTest *test = [[BRTest allocWithZone:zone] init];
test.name = [self.name mutableCopy];
return test;
}
@end
// viewController
BRTest *test = [[BRTest alloc] init];
test.name = @"测试";
BRTest *copyTest = [test copy];
BRTest *mutableTest = [test mutableCopy];
NSLog(@"%@===%@===%@",test.name,copyTest.name,mutableTest.name);
NSLog(@"%p====%p===%p",&test,©Test,&mutableTest);//变量本身的地址
NSLog(@"%p====%p===%p",test,copyTest,mutableTest);//变量的值指向的地址
NSLog(@"%p====%p===%p",test.name,copyTest.name,mutableTest.name);
copyTest.name = @"haha";
mutableTest.name = @"hello";
NSLog(@"%@===%@===%@",test.name,copyTest.name,mutableTest.name);
我们来看打印结果
我们从打印结果可以分析出自定义类使用copy是浅拷贝,使用mutableCopy是深拷贝,上面有几点需要注意
- 浅拷贝只是指针赋值,不开辟新的内存空间,所以我们在代码里 BRTest *test = self;是把当前类对象的指针赋值给test,类似于retain操作,引用计数+1
- 深拷贝是内容复制,需要开辟新的内存空间,所以我们在代码里BRTest *test = [[BRTest allocWithZone:zone] init];,并且对他的属性进行mutableCopy操作来达到深复制的效果,当然这里也是完全深复制了
- 不管是使用copyWithZone还是mutableCopyWithZone,都必须确定是否在类中实现浅复制或深复制,并为其编写文档,以告知类的其他使用者,因为在这两个方法中都能实现浅拷贝,单层深拷贝,完全深拷贝的效果。举个例子:
- (id)copyWithZone:(NSZone *)zone{
//BRTest *test = self;
BRTest *test = [[self class] allocWithZone:zone];
test.name = [self.name copy];
return test;
}
这样就变成了单层深复制了,因为他们的name的属性的内存地址是一样的,但是类的内的地址则不一样
- (id)mutableCopyWithZone:(NSZone *)zone{
//深拷贝
BRTest *test = [[BRTest allocWithZone:zone] init];
// test.name = [self.name mutableCopy];
test.name = [self.name copy];
return test;
}
这样写也变成了单层深拷贝了,所以说在copyWithZone还是mutableCopyWithZone代码里如何实现拷贝要看我们的具体需求
完全深复制的实现
//新建一个测试字符串
NSMutableString * str = [NSMutableString stringWithFormat:@"ludashi__"];
//新建一个测试字典
NSMutableDictionary *dic = [NSMutableDictionary dictionaryWithCapacity:1];
[dic setObject:str forKey:@"key1"];
//把字典存入数组中
NSMutableArray *oldArray = [NSMutableArray arrayWithObject:dic];
//用旧得数组生成新的数组
NSMutableArray *newArray = [NSMutableArray arrayWithArray:oldArray];
//用copyItems拷贝数组中的元素
NSMutableArray *copyItems = [[NSMutableArray alloc] initWithArray:oldArray copyItems:YES];
//把数组归档成一个NSData,然后再实现完全拷贝
NSData * data =
[NSKeyedArchiver archivedDataWithRootObject:oldArray];
NSMutableArray *multable = [NSKeyedUnarchiver unarchiveObjectWithData:data];
//往字典中加入新的值
[dic setObject:@"new_value1" forKey:@"key2"];
//改变str的值
[str appendString:@"update"];
NSLog(@"%@", oldArray);
NSLog(@"%@", newArray);
NSLog(@"%@", copyItems);
NSLog(@"%@", multable);
//每个数组的地址为:
NSLog(@"%p", oldArray);
NSLog(@"%p", newArray);
NSLog(@"%p", copyItems);
NSLog(@"%p", multable);
代码运行效果
2014-08-13 16:33:00.752 OC6-1[3942:303] (
{
key1 = "ludashi__update";
key2 = "new_value1";
}
)
2014-08-13 16:33:00.753 OC6-1[3942:303] (
{
key1 = "ludashi__update";
key2 = "new_value1";
}
)
2014-08-13 16:33:00.753 OC6-1[3942:303] (
{
key1 = "ludashi__update";
}
)
2014-08-13 16:33:00.753 OC6-1[3942:303] (
{
key1 = "ludashi__";
}
)
2014-08-13 16:33:00.754 OC6-1[3942:303] 0x100204560
2014-08-13 16:33:00.754 OC6-1[3942:303] 0x1002046d0
2014-08-13 16:33:00.754 OC6-1[3942:303] 0x1002047c0
2014-08-13 16:33:00.755 OC6-1[3942:303] 0x100406610
我们再来看看另外一个例子
NSMutableArray *array = [NSMutableArray array];
for (int i=0; i<5; i++) {
BRTest *test = [[BRTest alloc] init];
test.name = [NSString stringWithFormat:@"test%d",(i+1)];
[array addObject:test];
}
NSMutableArray *mutableArray = [array mutableCopy];
NSMutableArray *copyItems = [[NSMutableArray alloc] initWithArray:array copyItems:YES];
//数据归档再解档,实现完全拷贝
NSData *data = [NSKeyedArchiver archivedDataWithRootObject:array];
NSMutableArray *archiveArr = [NSKeyedUnarchiver unarchiveObjectWithData:data];
NSLog(@"%p",array);
NSLog(@"%p",mutableArray);
NSLog(@"%p",copyItems);
NSLog(@"%p",archiveArr);
NSLog(@"===========================================");
NSLog(@"===========================================");
NSLog(@"%p",array[0]);
NSLog(@"%p",mutableArray[0]);
NSLog(@"%p",copyItems[0]);
NSLog(@"%p",archiveArr[0]);
BRTest *test = array[0];
test.name = @"haha";
BRTest *test1 = copyItems[0];
test1.name = @"hello";
NSLog(@"===========================================");
NSLog(@"===========================================");
NSLog(@"%@",((BRTest *)array[0]).name);
NSLog(@"%@",((BRTest *)mutableArray[0]).name);
NSLog(@"%@",((BRTest *)copyItems[0]).name);
NSLog(@"%@",((BRTest *)archiveArr[0]).name);
代码运行结果
**2016-12-21 16:29:42.541 propertyDemo[2631:1572498] 0x60000005ec90
**2016-12-21 16:29:42.541 propertyDemo[2631:1572498] 0x60000005f290
**2016-12-21 16:29:42.542 propertyDemo[2631:1572498] 0x60000005f890
**2016-12-21 16:29:42.542 propertyDemo[2631:1572498] 0x60000005fda0
**2016-12-21 16:29:42.542 propertyDemo[2631:1572498] ===========================================
**2016-12-21 16:29:42.542 propertyDemo[2631:1572498] ===========================================
**2016-12-21 16:29:43.325 propertyDemo[2631:1572498] 0x600000009700
**2016-12-21 16:29:45.810 propertyDemo[2631:1572498] 0x600000009700
**2016-12-21 16:29:46.440 propertyDemo[2631:1572498] 0x600000009700
**2016-12-21 16:29:48.679 propertyDemo[2631:1572498] 0x600000009020
**2016-12-21 16:30:06.845 propertyDemo[2631:1572498] ===========================================
**2016-12-21 16:30:06.845 propertyDemo[2631:1572498] ===========================================
**2016-12-21 16:30:06.845 propertyDemo[2631:1572498] hello
**2016-12-21 16:30:06.845 propertyDemo[2631:1572498] hello
**2016-12-21 16:30:06.846 propertyDemo[2631:1572498] hello
**2016-12-21 16:30:06.846 propertyDemo[2631:1572498] test1
分析一下打印结果,从结果我们可以看到四个数组的内存地址各不相同,互相独立,但是我们通过打印每个数组的第一个元素的地址,我们发现前三个的地址都是一样的,这说明 [array mutableCopy]和
[[NSMutableArray alloc] initWithArray:array copyItems:YES]都是单层深复制,最后一个与前三个不一样,说明了利用数据归档再解档的方式实现了完全深复制,从后面第一次更改源头数组的第一个元素的属性的值,接着更改copyItems的第一个元素的值,而打印结果是前三个打印的值是第二次更改的值,最后一个则没有任何影响,这也进一步的说明了前三个数组的单层深复制,最后一个才是完全深复制