深拷贝与浅拷贝

对象拷贝有两种方式:浅拷贝和深拷贝。顾名思义,浅拷贝,并不拷贝对象本身,仅仅是拷贝指向对象的指针;深拷贝是直接拷贝整个对象内存到另一块内存中。

定义

浅拷贝 copy

  • 指针拷贝 - 将指针中的地址值拷贝一份.

  • 只复制指向对象的指针,而不复制引用对象本身。

  • copy返回的为imutable对象.如果对copy返回值使用mutable对象接口就会crash

深拷贝 mutableCopy

  • 对象拷贝 - 直接拷贝内容,内存中存在了两份独立对象本身。

  • mutableCopy返回mutable对象

  • 在多层数组中,对于被复制对象的每一层都是对象复制.

单层深拷贝

  • 这种方式只能够提供一层内存拷贝(one-level-deep copy),并非真正的深拷贝。

  • 在多层数组中,对第一层进行内容拷贝,其它层进行指针拷贝.在深复制操作时,对于被复制对象,至少有一层是深复制。

一:对非集合类对象的copy操作:

在非集合类对象中:

1、对不可变对象进行copy操作,是指针复制,mutableCopy操作时内容复制;

  • 不可变的非集合类对象的copy,对象的内存地址没有改变,只是指针的地址改变了。

  • 不可变非集合类对象的mutableCopy,对象的内存地址会改变。

2、对可变对象进行copy和mutableCopy都是内容复制,直接拷贝了对象,对象的内存地址会改变。

  • 进行可变对象的copy时,因为copy默认返回的是不可变的,所以对一个可变的字符串进行拷贝的时候,因为类型转变了,默认对其进行深拷贝,对象的内存地址会改变。

  • 对可变对象进行mutableCopy时,系统要保证旧的对象和新的对象都是可变的,且他们之间不会相互影响,因此对象的内存地址也会改变。

验证:

NSString*string =@"origin";
NSString*stringCopy = [string copy];//浅复制复制后的对象不可变
NSMutableString*mStringCopy = [string mutableCopy];//深复制复制后的对象可变
NSLog(@"%p - %p - %p", string, stringCopy, mStringCopy);

输出内容: 0x100001080 - 0x100001080 - 0x1003003e0

通过内存地址的打印可以看出,copy NSString,生成的新对象和原string的内存地址是一样的,copy NSMutableString 生成的新对象与原string的内存地址是不一样的。

NSMutableString*string2 = [NSMutableString stringWithString:@"origin2"];
NSString*stringCopy2 = [string2 copy];//深复制复制后的对象不可变
NSMutableString*mStringCopy2 = [string2 mutableCopy];//深复制复制后的对象可变
NSLog(@"%p - %p - %p", string2, stringCopy2, mStringCopy2);

输出内容: 0x100103920 - 0x326e696769726f75 - 0x1001039b0

可以看出mStringCopy2、stringCopy2和string2的内存地址都是不一样的,此时都是做内容拷贝。

二:集合类对象的copy与mutableCopy

在集合类对象中:

1、不可变对象进行copy操作,是指针拷贝,内存地址不更改.mutableCopy操作时,是内容拷贝,指针地址会更改.

2、可变的集合类对象, copy 与 mutableCopy 都是内容复制。

  • 集合对象的内容复制仅限于对象本身,对象元素仍然是指针复制。即这里的内容拷贝仅仅是拷贝"集合类对象"这个对象,集合内的元素仍然是指针拷贝。所以可以定义为是[单层深拷贝]。

验证:

NSArray* array =@[@"a", @"b", @"c", @"d"];
NSArray* copyArray = [array copy];//浅复制复制后的对象不可变
NSMutableArray* mCopyArray = [array mutableCopy];//单层深复制复制后的对象可变
NSLog(@"%p - %p - %p", array, copyArray, mCopyArray);

输出结果:0x100600450 - 0x100600450 - 0x1006038e0

可以看出:copy NSArray 生成的对象和原来的array内存地址是一样的,copy NSMutableArray 生成的对象和原来的array内存地址不一样。

NSMutableArray* array2 = [NSMutableArray arrayWithObjects: @"a", @"b", @"c", nil];
NSArray* copyArray2 = [array copy];//单层深复制复制后的对象不可变
NSMutableArray*mCopyArray2 = [array mutableCopy];//单层深复制复制后的对象可变
NSLog(@"%p - %p - %p", array2, copyArray2, mCopyArray2);

输出结果: 0x100106fd0 - 0x100600450 - 0x100107020

可以看出mCopyArray2、copyArray2和array2的内存地址都是不一样的。

三:集合类对象的深复制

集合对象的深复制有两种方法。以数组为例.

1.可以用 initWithArray:copyItems: 将第二个参数设置为YES即可深复制.

NSDictionary shallowCopyDict = [[NSDictionary alloc] initWithDictionary:someDictionary copyItems:YES];

使用这种方法深复制,集合里的每个对象都会收到 copyWithZone: 消息。

如果集合里的对象遵循 NSCopying 协议,那么对象就会被深复制到新的集合。

如果对象没有遵循NSCopying协议,而尝试用这种方法进行深复制,会在运行时出错。

copyWithZone: 这种拷贝方式只能够提供一层内存拷贝(one-level-deep copy),而非真正的深复制。

2.将集合进行归档(archive),然后解档(unarchive).

NSArray *trueDeepCopyArray = [NSKeyedUnarchiver unarchiveObjectWithData:[NSKeyedArchiver archivedDataWithRootObject:oldArray]];

其他

自定义对象的copy

在Objective-C中并不是所有的对象都支持Copy,MutableCopy,遵守NSCopying协议的类才可以发送Copy消息,遵守NSMutableCopying协议的类才可以发送MutableCopy消息。

NSString property属性为什么建议用copy不用strong

1、对源头是NSMutableString的字符串,retain仅仅是指针引用,增加了引用计数器,这样源头改变的时候,用这种retain方式声明的变量(无论被赋值的变量是可变的还是不可变的),它也会跟着改变;而copy声明的变量,它不会跟着源头改变,它实际上是深拷贝。

2、对源头是NSString的字符串,无论是retain声明的变量还是copy声明的变量,当第二次源头的字符串重新指向其它的地方的时候,它还是指向原来的最初的那个位置,也就是说其实二者都是指针引用,也就是浅拷贝。这两者对内存计数的影响都是一样的,都会增加内存引用计数,都需要在最后的时候做处理。

3、其实说白了,对字符串为啥要用这两种方式?还是因为安全问题,比如声明的一个NSString *str变量,然后把一个NSMutableString *mStr变量的赋值给它了,如果要求str跟着mStr变化,那么就用retain;如果str不能跟着mStr一起变化,那就用copy。而对于要把NSString类型的字符串赋值给str,那两都没啥区别。不会影响安全性,内存管理也一样。

Last updated