iOS中的深拷贝和浅拷贝的学习记录

时间:2022-01-27 22:00:17

最近想了解一下这方面的知识,所以特别记录一下


copy与retain的区别:

copy是创建一个新对象,retain是创建一个指针,引用对象计数加1。Copy属性表示两个对象内容相同,新的对象retain为1 ,与旧有对象的引用计数无关,旧有对象没有变化。copy减少对象对上下文的依赖。retain属性表示两个对象地址相同(建立一个指针,指针拷贝),内容当然相同,这个对象的retain值+1也就是说,retain 是指针拷贝,copy 是内容拷贝。


深拷贝的优势就是可以完全和之前的对象分离开来.


在ios中并不是所有的对象都支持copy,mutableCopy,遵守NSCopying 协议的类可以发送copy消息,遵守NSMutableCopying 协议的类才可以发送mutableCopy消息。假如发送了一个没有遵守上诉两协议而发送 copy或者 mutableCopy,那么就会发生异常。但是默认的ios类并没有遵守这两个协议。如果想自定义一下copy 那么就必须遵守NSCopying,并且实现 copyWithZone: 方法,如果想自定义一下mutableCopy 那么就必须遵守NSMutableCopying,并且实现 mutableCopyWithZone: 方法。


1.     系统的非容器类对象(NSString,NSNumber等等)


NSString *string = @"深拷贝与浅拷贝";
NSString *stringCopy = [string copy];
NSMutableString *stringMCopy = [string mutableCopy];

NSLog(@"%p",string);
NSLog(@"%p",stringCopy);
NSLog(@"%p",stringMCopy);

可以看到结果如下

iOS中的深拷贝和浅拷贝的学习记录


另外唠叨一句,如果此时打印retainCount你会发现计数是

iOS中的深拷贝和浅拷贝的学习记录


这是为什么呢,因为实际上NSString 已经autorelease了,就是说retainCount 对于autorelease消息产生的的对象,并不可靠。所以计数是没有意义的.


NSMutableString *string = [NSMutableString stringWithString: @"origion"];
NSString *stringCopy = [string copy];
NSMutableString *mStringCopy = [string copy];
NSMutableString *stringMCopy = [string mutableCopy];
[mStringCopy appendString:@"mm"];//error
[string appendString:@" origion!"];
[stringMCopy appendString:@"!!"];

NSLog(@"%p",string);
NSLog(@"%p",mStringCopy);
NSLog(@"%p",stringCopy);
NSLog(@"%p",stringMCopy);

iOS中的深拷贝和浅拷贝的学习记录


结论:以上四个NSString对象所分配的内存都是不一样的。但是对于mStringCopy其实是个不可变对象,所以上述会报错。对于系统的非容器类对象,我们可以认为,如果对一不可变对象复制,copy是指针复制(浅拷贝)和mutableCopy就是对象复制(深拷贝)。如果是对可变对象复制,都是深拷贝,但是copy返回的对象是不可变的。


2.     系统的容器类对象 

//copy返回不可变对象,mutablecopy返回可变对象
NSArray *array1 = [NSArray arrayWithObjects:@"a",@"b",@"c",nil];
NSArray *arrayCopy1 = [array1 copy];
//arrayCopy1是和array同一个NSArray对象(指向相同的对象),包括array里面的元素也是指向相同的指针
NSLog(@"array1 retain count: %d",[array1 retainCount]);
NSLog(@"array1 retain count: %d",[arrayCopy1 retainCount]);

NSLog(@"array1地址 : %p",array1);
NSLog(@"arrayCopy1地址 : %p",arrayCopy1);

NSMutableArray *mArrayCopy1 = [array1 mutableCopy];
//mArrayCopy1是array1的可变副本,指向的对象和array1不同,但是其中的元素和array1中的元素指向的是同一个对象。mArrayCopy1还可以修改自己的对象
[mArrayCopy1 addObject:@"de"];
[mArrayCopy1 removeObjectAtIndex:0];

NSLog(@"mArrayCopy1地址 : %p",mArrayCopy1);

NSLog(@"array1=%@",array1);
NSLog(@"arrayCopy1=%@",arrayCopy1);
NSLog(@"mArrayCopy1=%@",mArrayCopy1);

iOS中的深拷贝和浅拷贝的学习记录


array1和arrayCopy1是指针复制,而mArrayCopy1是对象复制,mArrayCopy1还可以改变期内的元素:删除或添加。但是注意的是,容器内的元素内容都是指针复制。


对于容器而言,其元素对象始终是指针复制。如果需要元素对象也是对象复制,就需要实现深拷贝


NSArray *array = [NSArray arrayWithObjects:[NSMutableString stringWithString:@"first"],@"b",@"c",nil];

NSArray *deepCopyArray=[[NSArray alloc] initWithArray: array copyItems: YES];

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

trueDeepCopyArray是完全意义上的深拷贝,而deepCopyArray则不是,对于deepCopyArray内的不可变元素其还是指针复制。或者我们自己实现深拷贝的方法。因为如果容器的某一元素是不可变的,那你复制完后该对象仍旧是不能改变的,因此只需要指针复制即可。除非你对容器内的元素重新赋值,否则指针复制即已足够。


3.     自定义对象

如果是我们定义的对象,那么我们自己要实现NSCopying,NSMutableCopying这样就能调用copy和mutablecopy了。

@interface MyObj : NSObject<NSCopying,NSMutableCopying>
{
NSMutableString *name;
NSString *imutableStr;
int age;
}
@property (nonatomic, retain) NSMutableString *name;
@property (nonatomic, retain) NSString *imutableStr;
@property (nonatomic) int age;
@end
@implementation MyObj
@synthesize name;
@synthesize age;
@synthesize imutableStr;
- (id)init
{
if (self = [super init])
{
self.name = [[NSMutableString alloc]init];
self.imutableStr = [[NSString alloc]init];
age = -1;
}
return self;
}
- (void)dealloc
{
[name release];
[imutableStr release];
[super dealloc];
}
- (id)copyWithZone:(NSZone *)zone
{
MyObj *copy = [[[self class] allocWithZone:zone] init];
copy->name = [name copy];
copy->imutableStr = [imutableStr copy];
// copy->name = [name copyWithZone:zone];;
// copy->imutableStr = [name copyWithZone:zone];//
copy->age = age;
return copy;
}
- (id)mutableCopyWithZone:(NSZone *)zone
{
MyObj *copy = NSCopyObject(self, 0, zone);
copy->name = [self.name mutableCopy];
copy->age = age;
return copy;
}

非指针型实例变量没有浅复制与深复制之分,像布尔型、整型、浮点型。浅复制与深复制是针对指针型实例变量说的,浅复制就只是复制了指针到副本中,原始对象与副本共享内存数据;深复制就是把内存的资源也复制了,原始对象和副本分别对应自己的内存数据。


最后再写一个例子

NSMutableDictionary *srcDic = [NSMutableDictionary dictionaryWithObjectsAndKeys:@"value", @"key", nil];  
NSMutableDictionary *dicCopy = [srcDic copy];//注意这里对NSMutableDictionary进行copy操作
NSLog(@"srcDic:%p, %@", srcDic, srcDic);
NSLog(@"dicCopy:%p %@", dicCopy, dicCopy);
[dicCopy setObject:@"newValue" forKey:@"key"];//尝试修改dicCopy里面的值

结果:
srcDic:0x7144ea0, {  
key = value;
}
dicCopy:0x71445c0 {
key = value;
}
[__NSDictionaryI setObject:forKey:]: unrecognized selector sent to instance 0x71445c0


//浅拷贝
NSArray *copyArr = [[NSArray alloc]initWithArray:@[@(1)]];
NSDictionary *copyDict = [[NSDictionary alloc]initWithDictionary:@{@"1":@(1),@"3":@(2)}];

//深拷贝
NSArray *copyArrDeep = [[NSArray alloc]initWithArray:@[@(1)] copyItems:YES];
NSDictionary *copyDictDeep =[[NSDictionary alloc]initWithDictionary:@{@"1":@(1),@"3":@(2)} copyItems:YES];

//归档
//保存
NSData *buffer = [NSKeyedArchiver archivedDataWithRootObject:@[@(1),@(2)]];
//恢复
NSArray *arr = [NSKeyedUnarchiver unarchiveObjectWithData:buffer];



结论:

由[srcDic mutableCopy]得到的dicCopy,两者内存地址不一致,即是copy对NSMutableDictionary类型进行了深复制,当尝试修改dicCopy里面的值时,发现报错了,修改不了,可以确定副本dicCopy是不可变副本。也就是说,copy一个Mutable类型可以深复制,但是其返回的 还是一个不可变的类型.


--------- 2月4日------------------------




//对mutable数组copy
NSMutableArray *a = [[NSMutableArray alloc]initWithObjects:@"1", nil];
NSLog(@"a-p %p",a);
NSLog(@"a %d",[a retainCount]);

NSArray *b = [a copy];
NSMutableArray *c = [a mutableCopy];

NSLog(@"a %d",[a retainCount]);
NSLog(@"b %d",[b retainCount]);
NSLog(@"c %d",[c retainCount]);

NSLog(@"a-p %p",a);
NSLog(@"b-p %p",b);
NSLog(@"c-p %p",c);

//对不可变数组进行copy
NSArray *a1 = [[NSArray alloc]initWithObjects:@"1", nil];
NSLog(@"a1-p %p",a1);
NSLog(@"a1 %d",[a1 retainCount]);

NSArray *b1 = [a1 copy];
NSMutableArray *c1 = [a1 mutableCopy];

NSLog(@"a1 %d",[a1 retainCount]);
NSLog(@"b1 %d",[b1 retainCount]);
NSLog(@"c1 %d",[c1 retainCount]);

NSLog(@"a1-p %p",a1);
NSLog(@"b1-p %p",b1);
NSLog(@"c1-p %p",c1);

iOS中的深拷贝和浅拷贝的学习记录


自定义对象copy或mutablecopy都会是深复制,copy是浅复制,mutableCopy是深复制,对容器而言,copy或mutableCopy一个可变的数组都是深复制,也就是说会出现新的内存地址,对不可变数组进行copy是指针复制,也就是浅复制,mutableCopy则是深复制,字符串亦是如此.但是如果copy一个mutable对象,用可变的对象去接收,例如copy一个mutable数组,用一个mutable数组接收,实际上是错误的,因为copy出来的是不可变的对象.


再加入一个深复制 浅复制的面试题

1.Difference between shallow copy and deep copy? 


浅复制和深复制的区别?


答案:浅层复制:只复制指向对象的指针,而不复制引用对象本身。


深层复制:复制引用对象本身。


意思就是说我有个A对象,复制一份后得到A_copy对象后,对于浅复制来说,A和A_copy指向的是同一个内存资源,复制的只不过是是一个指针,对象本身资源


还是只有一份,那如果我们对A_copy执行了修改操作,那么发现A引用的对象同样被修改,这其实违背了我们复制拷贝的一个思想。深复制就好理解了,内存中存在了


两份独立对象本身。


用网上一哥们通俗的话将就是:


浅复制好比你和你的影子,你完蛋,你的影子也完蛋


深复制好比你和你的克隆人,你完蛋,你的克隆人还活着。




参考文章:retainCount=1 ios复制--深复制,浅复制,自定义对象复制 IOS开发之深拷贝与浅拷贝(mutableCopy与Copy)详解