谈谈浅拷贝和深拷贝

2017-08-23  本文已影响32人  kamto

为什么回头再看这些基础知识呢,因为觉得好记性不如烂笔头。以前知其然不知所以然的东西,为了实现功能而实现功能而没有时间归纳总结,觉得还是用文章的形式记录下来很好,好了,闲话不多说,回归正题,当然同类文章比比皆是,但自己写一写跟看一看的感觉会不太一样,毕竟也算是自己的体会之一吧。

拷贝,就是复制对象。拷贝分两种,分别是浅拷贝、深拷贝。

那么copy和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框架的常见类:

另外提一点,我们比较提倡多用字面量语法创建这些类

例如:

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会复制所有内容,当集合元素还是集合,就应考虑归档解档实现完全深拷贝。

上一篇下一篇

猜你喜欢

热点阅读