谈谈浅拷贝和深拷贝
为什么回头再看这些基础知识呢,因为觉得好记性不如烂笔头。以前知其然不知所以然的东西,为了实现功能而实现功能而没有时间归纳总结,觉得还是用文章的形式记录下来很好,好了,闲话不多说,回归正题,当然同类文章比比皆是,但自己写一写跟看一看的感觉会不太一样,毕竟也算是自己的体会之一吧。
拷贝,就是复制对象。拷贝分两种,分别是浅拷贝、深拷贝。
-
浅拷贝
不会产生新的对象,指针指向的地址没有改变,也就是没有创建新的内存空间,即复制指针。
-
深拷贝
会产生新的对象,指针指向的地址会改变,也就是创建了新的内存空间,即内容拷贝。
那么copy和mutableCopy有什么区别呢
-
copy
不可变拷贝,对于不可变的Foundation框架的常见类,是不创建新内存空间的,对于可变的Foundation框架的常见类以及自定义的对象,会开辟一份内存空间给新对象。新对象都是不可变的。
-
mutableCopy
可变拷贝,其拷贝过程就是在内存中重新开辟一块区域,将对象复制一份放到这个区域。新对象是可以改变的,而且新对象的改变对原对象是没有影响的。
我们来以例子说明:
一、NSString
不可变字符串
NSString *str = @"test";
NSString *copyStr = [string copy];
NSMutableString *mutableCopyStr = [string mutableCopy];
NSLog(@"str:%@,%p",str,str);
NSLog(@"copyStr:%@,%p,%d",copyStr,copyStr,copyStr == str);
NSLog(@"mutableCopyStr:%@,%p,%d",muCopyStr,mutableCopyStr,mutableCopyStr == str);
打印:
str:test,0x10145b098
copyStr:test,0x10145b098,1
muCopyStr:test,0x7feb9355aed0,0
可变字符串
NSMutableString *str = [[NSMutableString alloc]initWithString:@"test"];
NSString *copyStr = [str copy];
NSMutableString *mutableCopyStr = [str mutableCopy];
NSLog(@"str:%@,%p",str,str);
NSLog(@"copyStr:%@,%p,%d",copyStr,copyStr,copyStr == str);
NSLog(@"mutableCopyStr:%@,%p,%d",mutableCopyStr,mutableCopyStr,mutableCopyStr == str);
打印:
str:test,0x7fbd90f0f640
copyStr:test,0xa000000747365744,0
muCopyStr:test,0x7fbd90f0be70,0
由此可见,对于不可变字符串,copy不会产生新的对象,mutableCopy却是会产生新的对象;对于可变字符串,copy和mutableCopy都是产生新的对象,只是copy产生一个不可变的新对象,而mutableCopy产生一个可变的新对象。
我们再来看看这个例子
NSString *str =@"test";
NSString *copyStr = [str copy];
NSMutableString *mutableCopyStr = [str mutableCopy];
str = @"test0";
//重新给Str赋值,实际重新给Str开辟一个内存空间,因此这一操作后实际这三个对象都是不一样的
NSLog(@"str:%@,%p",str,str);
NSLog(@"copyStr:%@,%p,%d",copyStr,copyStr,copyStr == str);
NSLog(@"mutableCopyStr:%@,%p,%d",mutableCopyStr,mutableCopyStr,mutableCopyStr == str);
str:test0,0x10745e218
copyStr:test,0x10745e098,0
mutableCopyStr:test,0x7fa15beab330,0
二、NSArray
NSArray和NSstring都是Foundation类的一种,所以情况都是一样
不可变数组
NSArray *arr =@[@"test"];
NSArray *copyArr = [arr copy];
NSMutableArray *mutableCopyArr = [arr mutableCopy];
NSLog(@"arr:%@,%p",arr,arr);
NSLog(@"copyArr:%@,%p,%d",copyArr,copyArr,copyArr == arr);
NSLog(@"mutableCopyArr:%@,%p,%d",mutableCopyArr,mutableCopyArr,mutableCopyArr == arr);
打印:
arr:(
test
),0x7fdc4161bbd0
copyArr:(
test
),0x7fdc4161bbd0,1
mutableCopyArr:(
test
),0x7fdc4161b160,0
可变数组
NSMutableArray *arr = [NSMutableArray arrayWithObjects:@"1",@"2",@"3",nil];
NSMutableArray *copyArr = [arr copy];
[copyArr removeLastObject];
Xcode崩溃
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[__NSArrayI removeLastObject]: unrecognized selector sent to instance 0x7fa2da614120'
Foundation框架常用的类有:NSNumber、NSString、NSArray、NSDictionary、NSMutableArray、NSMutableDictionay、NSMutableString等copy产生的对象时不可变的,mutableCopy产生的对象时可变的
NSMutableArray *arr = [NSMutableArray arrayWithObjects:@"1",@"2",@"3",nil];
NSArray *copyArr = [arr copy];
NSMutableArray *mutableCopyArr = [arr mutableCopy];
arr[0] = @"0";
NSLog(@"arr:%@,%p",arr,arr);
NSLog(@"copyArr:%@,%p,%d",copyArr,copyArr,copyArr == arr);
NSLog(@"mutableCopyArr:%@,%p,%d",mutableCopyArr,mutableCopyArr,mutableCopyArr == arr);
[mutableCopyArr addObject:@"hello"];
NSLog(@"mutableCopyArr:%@,%p,%d",mutableCopyArr,mutableCopyArr,mutableCopyArr == arr);
打印:
arr:(
0,
2,
3
),0x7f94fbe12550
copyArr:(
1,
2,
3
),0x7ffc12611020,0
mutableCopyArr:(
1,
2,
3
),0x7f94fbe79f80,0
对于Foundation框架的常见类:
-
不可变:(NS*)
-
copy,浅拷贝,不产生新对象
-
mutableCopy,深拷贝,产生新可变对象
-
-
可变:(NSMutable*)
-
copy,深拷贝,产生新不可变对象,
-
mutableCopy,深拷贝,产生新可变对象
-
另外提一点,我们比较提倡多用字面量语法创建这些类
例如:
NSMutableArray *arr = [@[] mutableCopy];
NSMutableDictionary *dict = [@{} mutableCopy];
三、自定义对象
先建一个Person类
@interface Person : NSObject
@property (nonatomic, copy)NSString *pid;
@property (nonatomic, copy)NSString *name;
@end
//初始化Person对象
Person *p = [[Person alloc] init];
p.name = @"joe";
p.pid = @"10";
Person *copyP = [p copy];
Xcode运行的话,不出意外肯定会报错
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[Person copyWithZone:]: unrecognized selector sent to instance 0x7ff643641780'
原因就是没有实现copyWithZone:这个方法
@protocol NSCopying
- (id)copyWithZone:(nullable NSZone *)zone;
@end
@protocol NSMutableCopying
- (id)mutableCopyWithZone:(nullable NSZone *)zone;
@end
command+左键可以得知这个方法是NSCopying里的协议方法,那么Person要先遵循NSCopying方法
@interface Person : NSObject<NSCopying>
@property (nonatomic, copy)NSString *pid;
@property (nonatomic, copy)NSString *name;
@end
@implementation Person
- (id)copyWithZone:(NSZone *)zone{
return @"test";
}
//初始化一个Person对象
Person *p = [[Person alloc] init];
p.name = @"joe";
p.pid = @"10";
Person *copyP = [p copy];
NSLog(@"p = %p copyP = %p", p, copyP);
p = 0x7fab996acf50 copyP = 0x10ab5c088
可以得出copyWithZone:(NSZone *)zone 实际为新对象重新分配了内存空间,由于返回是一个字符串,所以copyP实际是一个NSString类的对象
@implementation Person
- (id)copyWithZone:(NSZone *)zone{
//已给p分配了zone,就无需再alloc内存空间
// Person *p = [[Person alloc]init];
//如果Person类会被继承,那么将Person allocWithZone 改为[self class] allocWithZone:
// Person *p = [[Person allocWithZone:zone]init];
Person *p = [[[self class] allocWithZone:zone] init];
p.name = self.name;
p.pid = self.pid;
return p;
}
//初始化Person对象
Person *p = [[Person alloc] init];
p.name = @"joe";
p.pid = @"10";
Person *copyP = [p copy];
NSLog(@"p = %p copyP = %p", p, copyP);
NSLog(@"name = %@, pid = %@", copyP.name, copyP.pid);
NSLog(@"p.name地址:%p,copyP.name地址:%p",p.name,copyP.name);
p = 0x7fbfcb40e470 copyP = 0x7fbfcb418330
name = joe, pid = 10
p.name地址:0x10489d2a8,copyP.name地址:0x10489d2a8
copyWithZone
为浅拷贝,自定义对象重写该方法,当进行copy时为新对象分配了一个新的内存地址,mutableCopyWithZone
为深拷贝,也会为新对象分配一个新的内存地址,所以当[p copy]
时需要遵循 NSCopying
协议并重写copyWithZone
方法,当[p mutableCopy]
时需要遵循NSMutableCopying
并重写mutableCopyWithZone
,两者的写法也是一样的。
- (id)mutableCopyWithZone:(NSZone *)zone{
Person *p = [[[self class] allocWithZone:zone] init];
p.name = self.name;
p.pid = self.pid;
return p;
}
当子类继承父类时,子类也会遵循父类的NSCopying协议,这时可以不用再在子类里重写copyWithZone方法。
但是有个缺陷,虽然我们可以在父类里使用runtime遍历所有成员属性列表,然后赋值,但其父类的属性还是赋值不到。
@interface Person : NSObject<NSCopying>
@property (nonatomic, copy)NSString *pid;
@property (nonatomic, copy)NSString *name;
@end
@implementation Person
- (id)copyWithZone:(NSZone *)zone{
id obj = [[[self class]allocWithZone:zone]init];
unsigned int outCount;
Ivar * ivars = class_copyIvarList([self class], &outCount);
for (int i = 0; i < outCount; i ++) {
Ivar ivar = ivars[i];
NSString * key = [NSString stringWithUTF8String:ivar_getName(ivar)];
id value = [self valueForKey:key];
[obj setValue:value forKey:key];
}
free(ivars);
return obj;
}
@interface Student : Person
@property (nonatomic, copy) NSString *age;
@end
//初始化Student对象
Student *s = [[Student alloc]init];
s.name = @"marry";
s.age = @"18";
Student *copyS = [s copy];
NSLog(@"s = %p copyS = %p", s, copyS);
NSLog(@"name = %@ age = %@", copyS.name, copyS.age);
打印:
s = 0x7fa83a608ce0 copyS = 0x7fa83a61a100
name = (null) age = 18
这时候再来看看属性Strong和copy就容易理解了
例如
我们在Person类里再添加一条属性
@interface Person : NSObject<NSCopying>
@property (nonatomic, copy)NSString *pid;
@property (nonatomic, copy)NSString *name;
@property (nonatomic, copy) NSMutableArray *arr;
@end
//初始化Person对象
Person *p = [[Person alloc] init];
p.name = @"joe";
p.pid = @"10";
NSMutableArray *arr = [@[@"test"] mutableCopy];
p.arr = arr;
[arr addObject:@"hell0"];
Person *copyP = [p copy];
NSLog(@"arr:%p, p.arr:%@ %p,\n copyP.arr:%@ %p",arr,p.sign,p.arr,copyP.arr,copyP.arr);
arr:0x7fddea4358a0,p.arr:(
test
),0x7fddea43d0b0,
copyP.arr:(
test
),0x7fddea43d0b0
@property 本质是:ivar(实例变量)、存取方法(access method = getter + setter)
当用copy修饰时,self.arr实际实现这个setter方法
- (void)setArr:(NSMutableArray *)arr{
_arr = [arr copy];
}
[p.arr addObject:@"hell0"];
必然会报错误
[__NSArrayI addObject:]: unrecognized selector sent to instance 0x7ff3b94ee1a0'
所以self.arr 仍是不可变的,所以不要在@property添加可变属性
那我们用strong修饰试试
@property (nonatomic, strong) NSMutableArray *arr;
//初始化Person对象
Person *p = [[Person alloc] init];
p.name = @"joe";
p.pid = @"10";
NSMutableArray *arr = [@[@"test"] mutableCopy];
p.arr = arr;
[arr addObject:@"hell0"];
Person *copyP = [p copy];
NSLog(@"arr:%p, p.arr:%@ %p,\n copyP.arr:%@ %p",arr,p.sign,p.arr,copyP.arr,copyP.arr);
arr:0x7fbbf8503cc0,p.arr:(
test,
hell0
),0x7fbbf8503cc0,
copyP.arr:(
test,
hell0
),0x7fbbf8503cc0
实际用当用strong修饰时,self.arr实现这个setter方法
- (void)setArr:(NSMutableArray *)arr{
_arr = arr
}
所以用strong修饰时,self.arr 为可变数组,地址没有改变,当arr改变时,self.arr也会改变。
上面我们也注意到,copyWithZone: 产生的是浅复制,所以这种方法只能产生第一层深复制,如果集合内元素仍然是集合,则子集合内元素不会被深复制,只对子集合内元素指针进行复制。
我们看看这个例子
NSMutableString * testStr = [NSMutableString stringWithString:@"test"];
NSArray *arr = @[testStr];
NSArray *copyArr = [arr copy];
NSMutableArray *mutableCopyArr = [arr mutableCopy];
NSArray *arr2 = [[NSArray alloc]initWithArray:arr copyItems:YES];
[testStr appendString:@" hello"];
NSLog(@" arr.firstObject = %p, \n copyArr.firstObject= %p, \n mutableCopyArr.firstObject= %p, \n arr2.firstObject =%p",arr.firstObject, copyArr.firstObject, mutableCopyArr.firstObject, arr2.firstObject);
NSLog(@"arr %@, \n copyArr %@, \n mutableCopyArr %@, \n arr2 %@",arr, copyArr, mutableCopyArr, arr2);
打印:
arr.firstObject = 0x7fbfd27069a0,
copyArr.firstObject = 0x7fbfd27069a0,
mutableCopyArr.firstObject = 0x7fbfd27069a0,
arr2.firstObject = 0xa000000747365744
arr (
"test hello"
),
copyArr (
"test hello"
),
mutableCopyArr (
"test hello"
),
arr2 (
test
)
使用initWithArray: copyItems:YES,同initWithDictionary copyItems:YES
只能深拷贝到第2层,如果还有更深层的,还是不能完全深拷贝
NSMutableString * testStr = [NSMutableString stringWithString:@"test"];
NSMutableArray *testArr = [NSMutableArray arrayWithObject:testStr];
NSArray *arr = @[testArr];
NSArray *copyArr = [arr copy];
NSMutableArray *mutableCopyArr = [arr mutableCopy];
NSArray *arr2 = [[NSArray alloc]initWithArray:arr copyItems:YES];
[testStr appendString:@" hello"];
NSLog(@" arr.firstObject = %p, \n copyArr.firstObject= %p, \n mutableCopyArr.firstObject =%p, \n arr2.firstObject =%p",arr.firstObject, copyArr.firstObject, mutableCopyArr.firstObject, arr2.firstObject);
NSLog(@"arr %@, \n copyArr %@, \n mutableCopyArr %@, \n arr2 %@",arr, copyArr, mutableCopyArr, arr2);
打印:
arr.firstObject = 0x7fb50bf0cf60,
copyArr.firstObject = 0x7fb50bf0cf60,
mutableCopyArr.firstObject = 0x7fb50bf0cf60,
arr2.firstObject = 0x7fb50bf002f0
arr (
(
"test hello"
)
),
copyArr (
(
"test hello"
)
),
mutableCopyArr (
(
"test hello"
)
),
arr2 (
(
"test hello"
)
)
想实现完全深复制,应使用归档解档。使用归档和解档需要遵循NSCoding协议。
NSMutableString * testStr = [NSMutableString stringWithString:@"test"];
NSMutableArray *testArr = [NSMutableArray arrayWithObject:testStr];
NSArray *arr = @[testArr];
NSArray *copyArr = [arr copy];
NSMutableArray *mutableCopyArr = [arr mutableCopy];
NSArray *arr2 = [[NSArray alloc]initWithArray:arr copyItems:YES];
//完全深拷贝
NSArray *arr3 = [NSKeyedUnarchiver unarchiveObjectWithData:[NSKeyedArchiver archivedDataWithRootObject:arr]];
[testStr appendString:@" hello"];
id firstObject1 = [arr firstObject][0];
id firstObject2 = [copyArr firstObject][0];
id firstObject3 = [mutableCopyArr firstObject][0];
id firstObject4 = [arr2 firstObject][0];
id firstObject5 = [arr3 firstObject][0];
NSLog(@" arr.firstObject.firstObject = %p, \n copyArr.firstObject.firstObject = %p, \n mutableCopyArr.firstObject.firstObject = %p, \n arr2.firstObject.firstObject = %p,arr3.firstObject.firstObject = %p",firstObject1, firstObject2, firstObject3, firstObject4,firstObject5);
NSLog(@"arr %@, \n copyArr %@, \n mutableCopyArr %@, \n arr2 %@,\n arr3 %@",arr, copyArr, mutableCopyArr,arr2,arr3);
打印:
arr.firstObject.firstObject = 0x7fd4d271e8d0, copyArr.firstObject.firstObject = 0x7fd4d271e8d0,
mutableCopyArr.firstObject.firstObject = 0x7fd4d271e8d0,
arr2.firstObject.firstObject = 0x7fd4d271e8d0,
arr3.firstObject.firstObject = 0x7fd4d272b360
arr (
(
"test hello"
)
),
copyArr (
(
"test hello"
)
),
mutableCopyArr (
(
"test hello"
)
),
arr2 (
(
"test hello"
)
),
arr3 (
(
test
)
)
我们考虑深拷贝时,如果集合的结构元素不嵌套其他集合,mutableCopy会复制所有内容,当集合元素还是集合,就应考虑归档解档实现完全深拷贝。