iOS中拷贝的目的在于和源数据互不影响,修改源数据不会影响拷贝出来的对象,同样修改拷贝出来的对象也不会影响到源数据,记住这几条原则!
copy和mutableCopy
copy是拷贝出一个不可变数据
mutableCopy是拷贝出一个可变数据
下面看下例子,对不可变数据进行copy操作(浅拷贝,指向同一块内存区域)
#import <Foundation/Foundation.h>
int main(int argc,const char* argv[]) {
@autoreleasepool {
NSString*str1 =@"123";
NSString*str2 = [str1copy];
NSLog(@"%@,%@",str1,str2);
NSLog(@"%p,%p",str1,str2);
}
return 0;
}
打印结果:
iOS深拷贝和浅拷贝[1558:20300] 123,123
iOS深拷贝和浅拷贝[1558:20300] 0x100001038,0x100001038
可以看出,str1源数据是不可变数据,对不可变数据进行copy操作也会生成一个不可变数据,所以它们的值是一样的,但它们的地址也是一样的,也就是指向了同一块内存区域,感觉和之前说的互不影响冲突了!原因是由于不可变数据本身是不能被修改的,所以不存在哪一方被修改了会影响到另一方的说法,而且就算再重新分配一个内存来存放copy出来的数据,效果和这个也是一样,都是不能被修改的,所以编译器为了节省内存,让它们指向同一块内存
对不可变数据进行mutableCopy操作(深拷贝,指向不同的内存区域)
int main(int argc,const char* argv[]) {
@autoreleasepool {
NSString*str1 =@"123";
NSMutableString*str2 = [str1 mutableCopy];
[str2 appendString:@"456"];
NSLog(@"%@,%@",str1,str2);
NSLog(@"%p,%p",str1,str2);
}
return 0;
}
打印结果:
iOS深拷贝和浅拷贝[1886:25351] 123,123456
iOS深拷贝和浅拷贝[1886:25351] 0x100001040,0x10075e790
可以看出,对源数据为不可变数据进行mutableCopy操作后,会生成一个全新的可变数据,两个对象指向的是不同的内存区域,而且修改可变数据不会对源数据造成影响
对可变数据进行copy操作(深拷贝,指向不同的内存区域)
NSMutableString *str1 = [NSMutableString stringWithString:@"123"];
NSString*str2 = [str1copy];
NSLog(@"%@,%@",str1,str2);
NSLog(@"%p,%p",str1,str2);
打印结果:
iOS深拷贝和浅拷贝[1966:26578] 123,123
iOS深拷贝和浅拷贝[1966:26578] 0x1007577d0,0x3ee35425603786ad
可以看出对可变数据进行copy操作会生成一个全新的不可变数据,指向的是不同的内存区域
对可变数据进行mutableCopy操作(深拷贝,指向不同的内存区域)
NSMutableString *str1 = [NSMutableString stringWithString:@"123"];
NSMutableString*str2 = [str1mutableCopy];
[str2appendString:@"789"];
NSLog(@"%@,%@",str1,str2);
NSLog(@"%p,%p",str1,str2);
打印结果:
iOS深拷贝和浅拷贝[2053:28097] 123,123789
iOS深拷贝和浅拷贝[2053:28097] 0x10186d370,0x10186d430
可以看出对可变数据进行mutableCopy操作会生成一个全新的可变数据,指向的是不同的内存区域,而且修改全新的数据不会对源数据造成影响
对其他可变不可变数据(NSDictionary,NSMutableDictionary,NSArray,NSMutableArray,NSSet,NSMutableSet)进行操作也是一样的效果,可以自己尝试下!
结论:
对不可变数据进行copy操作就是浅拷贝,对不可变数据进行mutableCopy操作就是深拷贝!
对可变数据进行copy操作就是深拷贝,对可变数据进行mutableCopy操作也是深拷贝!
再看下自定义对象的copy操作
上面说的都是系统自带的类,而且系统自带的类都是遵守了NSCoping、NSMutableCopying协议
@interface NSString : NSObject <NSCopying, NSMutableCopying, NSSecureCoding>
但是自定义对象却没有,需要我们手动添加,如果直接使用自定义对象调用copy,会报一个经典错误unrecognized selector
iOS深拷贝和浅拷贝[2452:35056] -[WPPerson copyWithZone:]: unrecognized selector sent to instance 0x10051cc00
iOS深拷贝和浅拷贝[2452:35056] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[WPPerson copyWithZone:]: unrecognized selector sent to instance 0x10051cc00'
*** First throw call stack:
(
0 CoreFoundation 0x00007fff2fe9ccfd __exceptionPreprocess + 256
1 libobjc.A.dylib 0x00007fff5a546a17 objc_exception_throw + 48
2 CoreFoundation 0x00007fff2ff16b06 -[NSObject(NSObject) __retain_OA] + 0
3 CoreFoundation 0x00007fff2fe3eb8f ___forwarding___ + 1485
4 CoreFoundation 0x00007fff2fe3e538 _CF_forwarding_prep_0 + 120
6 libdyld.dylib 0x00007fff5bd143d5 start + 1
7 ??? 0x0000000000000001 0x0 + 1
)
libc++abi.dylib: terminating with uncaught exception of type NSException
原因是由于我们没有实现copyWithZone方法,查看源码可以看到copy方法其实是调用了copyWithZone方法
- (id)copy {
return [(id)self copyWithZone:nil];
}
先看下重写copyWithZone直接返回self,会发生什么情况
#import "WPPerson.h"
@interface WPPerson()<NSCopying>
@end
@implementation WPPerson
-(id)copyWithZone:(NSZone*)zone{
return self;
}
@end
WPPerson *p1 = [[WPPerson alloc] init];
WPPerson*p2 = [p1 copy];
NSLog(@"%@,%@",p1,p2);
打印结果:
iOS深拷贝和浅拷贝[2932:43861] <WPPerson: 0x10057f8d0>,<WPPerson: 0x10057f8d0>
可以看到p1和p2是指向的同一块内存地址,也就是浅拷贝,修改p1会对p2造成影响,所以copyWithZone方法里面可以随意控制你想要的copy机制,如果想要进行深拷贝,可以这样写
-(id)copyWithZone:(NSZone*)zone{
WPPerson*p = [WPPerson allocWithZone:zone];
return p;
}
WPPerson *p1 = [[WPPerson alloc] init];
WPPerson*p2 = [p1copy];
NSLog(@"%@,%@",p1,p2);
打印结果:
iOS深拷贝和浅拷贝[3118:46781] <WPPerson: 0x10180b090>,<WPPerson: 0x10180b7d0>
可以看出p1和p2指向的是不同的内存地址,但我想要将p1对象里面的属性值也拷贝到p2中,又该怎么做呢?
-(id)copyWithZone:(NSZone*)zone{
WPPerson*p = [WPPerson allocWithZone:zone];
p.age=self.age;
p.name=self.name;
return p;
}
-(NSString*)description{
return [NSString stringWithFormat:@"name == %@,age == %d,address == %p",self.name,self.age,self];
}
WPPerson *p1 = [[WPPerson alloc] init];
p1.name=@"jerry";
p1.age=15;
WPPerson*p2 = [p1 copy];
NSLog(@"%@,%@",p1,p2);
p2.name=@"tom";
NSLog(@"%@,%@",p1,p2);
打印结果:
iOS深拷贝和浅拷贝[3244:49034] name == jerry,age == 15,address == 0x10078aa20,name == jerry,age == 15,address == 0x10078b1f0
iOS深拷贝和浅拷贝[3244:49034] name == jerry,age == 15,address == 0x10078aa20,name == tom,age == 15,address == 0x10078b1f0
可以在copyWithZone方法中直接进行属性赋值,而且修改p1,p2是不会受到影响
如果p1里面有集合这种数据属性,copy之后想做到完全深拷贝,又该如何做呢?如果按照之前的简单赋值,看下会发生什么情况
-(id)copyWithZone:(NSZone*)zone{
WPPerson*p = [WPPerson allocWithZone:zone];
p.age=self.age;
p.name=self.name;
p.arrData = self.arrData;
returnp;
}
-(NSString*)description{
return [NSString stringWithFormat:@"name == %@,age == %d,arrData == %@,address == %p",self.name,self.age,self.arrData,self];
}
-(NSMutableArray *)arrData{
if(_arrData==nil) {
_arrData= [NSMutableArray array];
}
return _arrData;
}
WPPerson *p1 = [[WPPerson alloc] init];
p1.name=@"jerry";
p1.age=15;
[p1.arrData addObject:@"111"];
WPPerson*p2 = [p1 copy];
NSLog(@"%@,%@",p1,p2);
p2.name=@"tom";
[p2.arrData addObject:@"222"];
NSLog(@"%@,%@",p1,p2);
打印结果:
name == jerry,age == 15,arrData == (
111
),address == 0x100704870,
name == jerry,age == 15,arrData == (
111
),address == 0x100705100
name == jerry,age == 15,arrData == (
111,
222
),address == 0x100704870,name == tom,age == 15,arrData == (
111,
222
),address == 0x100705100
可以看出当改变p2中的集合数据时,p1中的数据也跟着发生改变,因此自定义对象中如果有集合这种数据类型时,使用简单的赋值并不能做到完全深拷贝,所以只需要在赋值操作时通过mutableCopy拷贝出一个全新的集合对象,这样就达到了完全深拷贝!
-(id)copyWithZone:(NSZone*)zone{
WPPerson*p = [WPPerson allocWithZone:zone];
p.age=self.age;
p.name=self.name;
p.arrData= [self.arrData mutableCopy];
return p;
}
打印结果:
name == jerry,age == 15,arrData == (111
),address == 0x100786df0,name == jerry,age == 15,arrData == (
111
),address == 0x100787680
name == jerry,age == 15,arrData == (
111
),address == 0x100786df0,name == tom,age == 15,arrData == (
111,
222
),address == 0x100787680
最后再看一种特殊的情况,自定义对象中有一个集合属性,而且集合中装的都是另一个自定义对象,看代码
WPCar.h文件
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface WPCar : NSObject
@property(nonatomic,assign)int weight;
@end
NS_ASSUME_NONNULL_END
WPCar.m文件
#import "WPCar.h"
@implementation WPCar
-(NSString *)description{
return[NSString stringWithFormat:@"weight == %d,address == %p",self.weight,self];
}
@end
WPPerson.h文件
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interfaceWPPerson :NSObject
@property(nonatomic,strong)NSMutableArray *arrData;
@end
NS_ASSUME_NONNULL_END
WPPerson.m文件
#import "WPPerson.h"
@interface WPPerson()<NSCopying>
@end
@implementation WPPerson
-(id)copyWithZone:(NSZone*)zone{
WPPerson*p = [WPPerson allocWithZone:zone];
p.arrData= [self.arrData mutableCopy];
returnp;
}
-(NSString*)description{
return [NSString stringWithFormat:@"arrData == %@,address == %p",self.arrData,self];
}
-(NSMutableArray *)arrData{
if(_arrData==nil) {
_arrData= [NSMutableArray array];
}
return _arrData;
}
@end
再看下外部使用情况
WPCar*car1 = [[WPCar alloc]init];
car1.weight=11;
WPPerson*p1 = [[WPPerson alloc]init];
[p1.arrData addObject:car1];
WPPerson*p2 = [p1copy];
NSLog(@"%@,%@",p1,p2);
WPCar*car2 = [[WPCar alloc]init];
[p2.arrData addObject:car2];
WPCar*carTmp = p2.arrData[0];
carTmp.weight=22;
NSLog(@"%@,%@",p1,p2);
打印结果:
arrData == (
"weight == 11,car_address == 0x100624af0"
),person_address == 0x100624900,
arrData == (
"weight == 11,car_address == 0x100624af0"
),person_address == 0x100604590
arrData == (
"weight == 22,car_address == 0x100624af0"
),person_address == 0x100624900,
arrData == (
"weight == 22,car_address == 0x100624af0",
"weight == 0,car_address == 0x10054e860"
),person_address == 0x100604590
可以看出p1和p2是不同的两个对象,而且p1和p2中的数组也是指向的不同内存区域,但是数组里面装的car对象却是指向的同一块内存区域,当我们修改p2中arrData数组中的car对象的weight属性时,p1中的weight也发生了变化,这样也达不到我们想要的需求,这时就需要用到[[NSMutableArray alloc] initWithArray:self.arrData copyItems:YES];方法来替代之前的mutableCopy了,该方法中的copyItems如果传入的是YES,那么表示该数组中的每个元素都会调用copy,所以需要WPCar对象也遵循NSCoping协议以及重写copyWithZone方法
#import "WPCar.h"
@interfaceWPCar()
@end
@implementation WPCar
-(id)copyWithZone:(NSZone *)zone{
WPCar *p = [WPCar allocWithZone:zone];
returnp;
}
#import "WPPerson.h"
@interface WPPerson()<NSCopying>
@end
@implementation WPPerson
-(id)copyWithZone:(NSZone*)zone{
WPPerson*p = [WPPerson allocWithZone:zone];
p.arrData = [[NSMutableArray alloc] initWithArray:self.arrData copyItems:YES];
// p.arrData = [self.arrData mutableCopy];
returnp;
}
打印结果:
arrData == (
"weight == 11,address == 0x10058eba0"
),address == 0x10058e9b0,
arrData == (
"weight == 0,address == 0x10057f8a0"
),address == 0x100580e70
arrData == (
"weight == 11,address == 0x10058eba0"
),address == 0x10058e9b0,
arrData == (
"weight == 22,address == 0x10057f8a0",
"weight == 0,address == 0x100609630"
),address == 0x100580e70
这样就可以做到对自定义对象WPPerson中的集合对象(包含其他自定义对象WPCar)也完全深拷贝了!
网友评论