上次面试被问到怎么去完全复制?懵逼了啊,之前完全没注意到过这个啊,所以就把问题记下来,好好花了个时间整理了一下,写的可能有点儿啰嗦,将就看吧。
在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修饰。
最后block声明也是使用的Copy,具体的看 Block使用注意事项。
放个网上别人做的汇总图:
1470132795549883.jpg
网友评论