详解iOS的深浅拷贝

上次面试被问到怎么去完全复制?懵逼了啊,之前完全没注意到过这个啊,所以就把问题记下来,好好花了个时间整理了一下,写的可能有点儿啰嗦,将就看吧。

在iOS里,copy与mutableCopy都是NSObject里的方法,一个NSObject的对象要想使用这两个函数,那么类必须实现NSCopying协议或NSMutableCopying协议,一般来说我们用的很多系统里的容器类已经实现了这些方法。

  • 非集合类对象的copy与mutableCopy

系统非集合类对象指的是 NSString, NSNumber … 之类的对象。对immutable(不可变)对象进行copy操作,是指针拷贝,mutableCopy(可变)操作时内容拷贝;对mutable对象进行copy和mutableCopy都是内容拷贝。用代码简单表示如下:

[immutableObject copy] // 浅拷贝  
[immutableObject mutableCopy] //深拷贝  
[mutableObject copy] //深拷贝  ****注意:mutableObject使用copy都是深拷贝***
[mutableObject mutableCopy] //深拷贝  
  • 集合类对象的copy与mutableCopy

集合类对象是指NSArray、NSDictionary、NSSet … 之类的对象。对immutable对象进行copy,是指针拷贝,mutableCopy是内容拷贝;对mutable对象进行copy和mutableCopy都是内容拷贝。但是:集合对象的内容拷贝仅限于对象本身,对象元素仍然是指针拷贝。用代码简单表示如下:

[immutableObject copy] // 浅拷贝
[immutableObject mutableCopy] //单层深拷贝
[mutableObject copy] //单层深拷贝
[mutableObject mutableCopy] //单层深拷贝
执行 结果
对immutableObject,即不可变对象,执行copy 会得到不可变对象,并且是浅copy。
对immutableObject,即不可变对象,执行mutableCopy 会得到可变对象,并且是深copy。
对mutableObject,即可变对象,执行copy 会得到不可变对象,并且是深copy。
对mutableObject,即可变对象,执行mutableCopy 会得到可变对象,并且是深copy。
  • 以上,我们可以发现,不管在非集合类对象中,还是在集合类中,对不可变(immutableObject)对象进行copy操作,只仅仅是指针复制,进行mutableCopy操作,是内容复制。对可变对象(mutableObject)进行copy和mutableCopy操作,都是内容复制。
  • 但是,集合类里的深拷贝和非集合类的深拷贝还是不太一样的,当我们对集合类进行mutableCopy操作时,虽然数组内存地址发生了变化,但是数组元素的内存地址并没有发生变化。这个属于一个特例,我们称它为单层深复制。并不是理论上的完全深复制
    • 这里就引入一个新概练:深复制(单层深复制)和完全复制。

我们知道深复制,就是把原有对象的内容直接克隆一份到新对象,但是这里有一个坑就是他只会复制一层对象,而不会复制第二层甚至更深层次的对象,例如数组元素中的对象,看代码:

 NSMutableArray * dataArray1=[NSMutableArray arrayWithObjects:[NSMutableString stringWithString:@"one"],@"two", @"three",  @"four",nil];
 NSMutableString * mStr;
//1、dataArray1使用mutableCopy深拷贝
NSMutableArray * dataArray2 = [dataArray1 mutableCopy];
 //2、可变的dataArray1使用copy都是深拷贝
 NSArray * array= [dataArray1 copy];

//3、修改原数组dataArray1中可变对象NSMutableString数据
mStr = dataArray1[0];
[mStr appendString:@"-add some data"];
//4、验证array、dataArray2是否改变
NSLog(@"dataArray1:%@",dataArray1);
NSLog(@"dataArray2:%@",dataArray2);
NSLog(@"array:%@",array);

**打印结果:**
dataArray1::( "one-add some data",  two,  three, four)
dataArray2::( "one-add some data",  two,  three, four)
array::( "one-add some data",  two,  three, four)

从上面的输出可以看出,修改了dataArray1中的NSMutableString对象元素的值,dataArray2和array都发生了改变,dataArray2=[dataArray1 mutableCopy];只是对数组dataArray1本身进行了内容深拷贝,但是里面的字符串对象却没有进行内容拷贝,而是进行的浅复制,那么dataArray1和dataArray2里面的对象是共享同一份的。所以才会出现上面的情况。

使用下面这种方法可以解决上面的问题:

把
  NSMutableArray * dataArray2 = [dataArray1 mutableCopy];
替换成
  NSMutableArray * dataArray2 = [[NSMutableArray alloc]initWithArray:dataArray1 copyItems:YES];

**再次打印结果:**
dataArray1:(  "one-add some data",two,  three, four)
dataArray2:(  one, two,three, four)
array:    ( "one-add some data",two, three, four)

注意:此方法使元素中均执行[xxx copy]方法。这也是我在dataArray1中放入NSMutableString的原因。如果我放入的是NSArray或者NSString,执行copy后,只会发生指针复制;如果我放入的是未实现NSCopying协议的对象,如自定义对象,调用这个方法甚至会crash,因为自定义对象没有copy方法。

方式可行,对第二层也做了深复制,但是如果把dataArray1添加到一个可变对象dataArray4中,上面的方法就是失效了,如下代码:

NSMutableArray * dataArray1=[NSMutableArray arrayWithObjects:
                                 [NSMutableString stringWithString:@"one"],
                                 @"two",
                                 @"three",
                                 @"four",nil];
NSMutableArray * dataArray4 = [NSMutableArray arrayWithObjects:  dataArray1,
                                 [NSMutableString stringWithString:@"1"],
                                 [NSMutableString stringWithString:@"2"],
                                 nil
                                 ];
    
NSMutableArray * dataArray5 = [[NSMutableArray alloc]initWithArray:dataArray4 copyItems:YES];
NSMutableString * mStr;  
//修改数组dataArray4中的dataArray1中的可变对象NSMutableString数据
NSMutableArray * tempMu = dataArray4[0];
mStr = tempMu[0];
[mStr appendString:@"-add some data too"];
 
NSLog(@"dataArray5:%@",dataArray5);
NSLog(@"dataArray4:%@",dataArray4);
**打印结果:**
dataArray4:( (  "one-add some data too", two, three, four ),1, 2)
dataArray5:( (  "one-add some data too", two, three, four ),1, 2)

然而深复制又失效了,这是因为dataArray5= [[NSMutableArray alloc]initWithArray:dataArray4 copyItems:YES];仅仅能进行一层深复制,对于第二层或者更多层的就无效了。
要想对多层集合对象进行复制,我们需要进行完全复制,这里可以使用归档和接档

把
NSMutableArray * dataArray5 = [[NSMutableArray alloc]initWithArray:dataArray4 copyItems:YES];
替换成
NSMutableArray * dataArray5  = [NSKeyedUnarchiver unarchiveObjectWithData:[NSKeyedArchiver archivedDataWithRootObject:dataArray4]];
**打印结果:**
dataArray5:(
    (
        one, two, three,four
    ), 
    1,2)
 dataArray4:(
   (
        "one-add some data too", two, three, four
    ),
    1, 2 )

总结
浅复制(shallow copy) 在浅复制操作时,对于被复制对象的每一层都是指针复制 .
深复制(one-level-deep copy) 在深复制操作时,对于被复制对象,至少有一层是深复制。
完全复制(real-deep copy) 在完全复制操作时,对于被复制对象的每一层都是对象复制。
  • 自定义类的深浅copy

一个NSObject的对象要想使用拷贝,那么类必须实现NSCopying协议或NSMutableCopying协议,如果不遵守协议,直接使用[xxx copy]、[xxx mutableCopy],那么会直接导致程序崩溃,比如自定义一个UserInfo的类,这个类就不允许直接使用[UserInfo copy]。让自定义的对象进行copy与mutableCopy,需要做以下事情:

1.让类实现NSCopying/NSMutableCopying协议。
 2.让类实现copyWithZone:/mutableCopyWithZone:方法

举例:

自定义类:
.h
@interface UserInfo : NSObject
@property (nonatomic,copy) NSString * name;
@property (nonatomic,assign) NSInteger age;
@property (nonatomic,strong) NSMutableArray * photos;
@end
.m
// 当对象需要调用 copy 的时候,需要调用 copyWithZone:这个方法
//写法一:这样写就是单层深复制
-(id)copyWithZone:(NSZone *)zone{
    UserInfo * userInfo= [[UserInfo allocWithZone:zone] init];
    userInfo.age = self.age;
    userInfo.name = self.name;
    userInfo.photos = self.photos;
    return userInfo;
}
//写法二:这样写就是浅复制
- (id)copyWithZone:(NSZone *)zone {
    return self;
}

ViewController中调用测试
    NSMutableString * muString = [NSMutableString stringWithFormat:@"abc"];
    NSMutableArray * muArray = [NSMutableArray arrayWithObjects:muString,@"1",@"2",@"3", nil];
    
    UserInfo * user1 = [[UserInfo alloc]init];
    user1.name = @"xixi";
    user1.age =  20;
    user1.photos = muArray;
    
    //先copy一份
    UserInfo * user2 = [user1 copy];
    NSMutableArray * muArray2 = user2.photos;
    
    //再修改数据
    NSMutableString * muString2 = muArray[0];
    [muString2 appendString:@"000"];
    
    NSLog(@"user1 内存地址%p",user1);
    NSLog(@"user2 内存地址%p",user2);
    NSLog(@"muArray:%@",muArray);
    NSLog(@"muArray2:%@",muArray2);
**打印结果:**
user1 内存地址 :0x60400022a440
user2 内存地址 :0x60400022d920
muArray: (
    abc000,
    1,
    2,
    3
)
muArray2:(
    abc000,
    1,
    2,
    3
)
user1与user2地址不同,但是在改变元数据中的可变对象muString后,user2数据也改变了,所以这就是咱们上面提到的单层深拷贝,如果需要实现完全复制,用归档。

关于mutableCopy的实现与copy的实现类似,只是实现的是NSMutableCopying协议与mutableCopyWithZone:方法。对于自定义的对象,在我看来并没有什么可变不可变的概念,因此实现mutableCopy其实是没有什么意义的。
如果是自定义的类,使用copy是到底是浅拷贝还是单层深拷贝,主要是看你怎么去实现这个- (id)copyWithZone:(NSZone *)zone方法了。

注意:定义合成getter、setter方法时并没有提供mutableCopy指示符。因此即使定义实例变量时使用了可变类型,但只要使用copy指示符,实例变量实际得到的值总是不可变对象

  • Copy关键字使用说明

在平时定义属性的时候,对于NSString、NSArray、NSDictionary类型的属性,我们最好设置为copy类型,这样别人使用我们定义的属性的时候,它不管怎么改动该属性的赋值,都不会影响我们给该属性赋的值。
例如:

@property (nonatomic,strong) NSArray * arraySt;
@property (nonatomic,copy) NSArray * arrayCp;

NSMutableArray * mu = [NSMutableArray arrayWithObjects:@"1",@"2",@"3", nil];
 self.arraySt = mu ;
 self.arrayCp = mu;
//修改赋值数据
[mu addObject:@"acvadda"];
NSLog(@"self.arraySt %@",self.arraySt);
NSLog(@"self.arrayCp %@",self.arrayCp);
**打印结果:**
//用strong变
self.arraySt (
    1, 2,3,acvadda
)
//用copy没变
 self.arrayCp (
1,2, 3
)

当使用 copy 修饰属性的时候,属性的setter方法会调用[object copy]产生新的对象,
这样,当原object对象的值发生改变时,并不影响新对象值;

// 定义NSString
 @property(nonatomic, copy) NSString *name;
// 当使用的copy的时候,等价于下面的代码
- (void)setName:(NSString *)name {
      if (_name != name) {
         [_name release];
          _name = [name copy];
       }
}

copy关键字的string的setter方法实际上是把参数copy之后再赋值给变量_string,那么此时变量_string虽然被申明为NSMutableString,但是copy之后,就把 变量_string变成了不可变的NSString类型

当使用 strong 修饰属性的时候,属性的setter方法会直接强引用该对象,这样,当原object对象的值发生改变时,新对象的属性也改变;

但是对于可变对象类型,如NSMutableString、NSMutableArray等则不可以使用copy修饰,因为Foundation框架提供的这些类都实现了NSCopying协议,使用copy方法返回的都是不可变对象,如果使用copy修饰符在对可变对象赋值时则会获取一个不可变对象,接下来如果对这个对象进行可变对象的操作则会产生异常,因为OC没有提供mutableCopy修饰符,对于可变对象使用strong修饰符即可。
例如:使用strong修饰的NSMutableArray,这个可变数组在当前文件中只有一个,而且是可变的;

/** 数组 */
@property(nonatomic,strong)NSMutableArray *array;

// 当使用strong的时候,等价于下面的代码
-(void)setArray:(NSMutableArray *)array{
    _array = array;
}

总结
针对不可变对象使用copy修饰,针对可变对象使用strong修饰。

详解iOS的深浅拷贝_第1张图片
a 由可变变成不可变了,错误代码

最后block声明也是使用的Copy,具体的看 Block使用注意事项。

放个网上别人做的汇总图:


详解iOS的深浅拷贝_第2张图片
1470132795549883.jpg

你可能感兴趣的:(详解iOS的深浅拷贝)