iOS开发-iOS中的拷贝

今天来说一下iOS中的copy

在iOS中,拷贝有两种方式,深拷贝(Deep copy)和浅拷贝(Shallow copy)。先上一张苹果官方的图来表示一下这两种拷贝。

logo

可以看到,深拷贝是直接拷贝整个对象内存到另一块内存中。浅拷贝则是仅仅拷贝一份指向对象的指针,而并不拷贝对象本身。

有大佬总结的一句话就是,深拷贝就像克隆你的人,你人没有了,你的克隆人还在,而浅拷贝就像是克隆你的影子,你人如果没有了,那你的影子也就没有了。

但是上边说的深拷贝,其实也分成两种,一种是单层深拷贝(one-level-deep copy),还有一种是完全深拷贝(true deep copy)。

苹果官方有这样一句话:

This kind of copy is only capable of producing a one-level-deep copy. If you only need a one-level-deep copy, you can explicitly call for one as in Listing 2.

1
2
> NSArray *deepCopyArray=[[NSArray alloc] initWithArray:someArray copyItems:YES];
>

If you need a true deep copy, such as when you have an array of arrays, you can archive and then unarchive the collection, provided the contents all conform to the NSCoding protocol. An example of this technique is shown in Listing 3.

1
2
3
4
> NSArray* trueDeepCopyArray = [NSKeyedUnarchiver unarchiveObjectWithData:
[NSKeyedArchiver archivedDataWithRootObject:oldArray]];
>

说白了,单层深拷贝和完全深拷贝从名字就能看出来,只有一层是深复制,而另外一个是每一层都是深复制。

了解了深浅拷贝的概念,如何使用我们就不过多介绍了,官方文档里都有写的很清楚,那接下来我们来讨论一下开发中用到的各种类型的对象的拷贝吧。

首先我们先把我们平时用到的对象进行一个分类,分成集合类型、非集合类型和自定义类型,其中集合类型就是NSArray、NSDictionary这种,非集合类型就是NSString、NSNumber这种。

这些类型也都有对应的可变类型,比如NSMutableArray、NSMutableDictionary、NSMutableString…

而拷贝也分为两种,有mutableCopy和copy。

我们在集合类型中选出数组NSArray作为代表,非集合类型中选出NSString作为代表,来研究一下不同情况下的拷贝是深拷贝还是浅拷贝,以及拷贝出来的对象是什么类型。排列组合一下就有如下几种:

  • 集合类型 + 可变类型 + mutableCopy
  • 集合类型 + 可变类型 + copy
  • 集合类型 + 不可变类型 + mutableCopy
  • 集合类型 + 不可变类型 + copy
  • 非集合类型 + 可变类型 + mutableCopy
  • 非集合类型 + 可变类型 + copy
  • 非集合类型 + 不可变类型 + mutableCopy
  • 非集合类型 + 不可变类型 + copy
  • 自定义类型 + mutableCopy
  • 自定义类型 + copy

集合类型的拷贝

集合类型 + 可变类型

集合类型 + 可变类型 + mutableCopy

先上代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
/**
集合类型 - 可变类型 - mutableCopy
*/
- (void)setTypeMutableTypeMutableCopy
{
NSMutableArray *mutableArr = [NSMutableArray arrayWithObjects:@"first",@"second",@"third", nil];
NSMutableArray *mutableCopyMutableArr = [mutableArr mutableCopy];
NSLog(@"%@",[[mutableArr mutableCopy] class]);
NSLog(@"%@ --- %p",mutableArr,mutableArr);
NSLog(@"%@ --- %p",mutableCopyMutableArr ,mutableCopyMutableArr);
}

打印结果:

1
2
3
4
5
6
7
8
9
10
11
2018-04-05 11:04:59.655093+0800 CopyDemo[11202:404553] __NSArrayM
2018-04-05 11:04:59.655338+0800 CopyDemo[11202:404553] (
first,
second,
third
) --- 0x60000024c060
2018-04-05 11:04:59.655475+0800 CopyDemo[11202:404553] (
first,
second,
third
) --- 0x60000024c5a0

可以看到结果,这种组合的情况下拷贝出的对象是一个可变数组(__NSArrayM),而两个打印出的数组的地址不同,所以是深拷贝。

集合类型 + 可变类型 + copy

先上代码:

1
2
3
4
5
6
7
8
9
10
11
12
/**
集合类型 - 可变类型 - imutableCopy
*/
- (void)setTypeMutableTypeImutableCopy
{
NSMutableArray *mutableArr = [NSMutableArray arrayWithObjects:@"first",@"second",@"third", nil];
NSMutableArray *mutableCopyImutableArr = [mutableArr copy];
NSLog(@"%@",[[mutableArr copy] class]);
NSLog(@"%@ --- %p",mutableArr,mutableArr);
NSLog(@"%@ --- %p",mutableCopyImutableArr ,mutableCopyImutableArr);
}

打印结果:

1
2
3
4
5
6
7
8
9
10
11
2018-04-05 11:04:59.655633+0800 CopyDemo[11202:404553] __NSArrayI
2018-04-05 11:04:59.655754+0800 CopyDemo[11202:404553] (
first,
second,
third
) --- 0x604000250ef0
2018-04-05 11:04:59.655966+0800 CopyDemo[11202:404553] (
first,
second,
third
) --- 0x604000251400

可以看到结果拷贝出的是一个不可变数组(__NSArrayI),另外两个数组的地址也不同,所以是深拷贝。

集合类型 +不可变类型

集合类型 + 不可变类型 + mutableCopy

先上代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
/**
集合类型 - 不可变类型 - mutableCopy
*/
- (void)setTypeImutableTypeMutableCopy
{
NSArray *imutableArr = [NSArray arrayWithObjects:@"first",@"second",@"third", nil];
NSArray *imutableArrMutableCopy = [imutableArr mutableCopy];
NSLog(@"%@",[[imutableArr mutableCopy] class]);
NSLog(@"%@ --- %p",imutableArr,imutableArr);
NSLog(@"%@ --- %p",imutableArrMutableCopy,imutableArrMutableCopy);
}

打印结果:

1
2
3
4
5
6
7
8
9
10
11
2018-04-05 11:04:59.656128+0800 CopyDemo[11202:404553] __NSArrayM
2018-04-05 11:04:59.656245+0800 CopyDemo[11202:404553] (
first,
second,
third
) --- 0x604000251400
2018-04-05 11:04:59.656357+0800 CopyDemo[11202:404553] (
first,
second,
third
) --- 0x604000251190

可以看到,拷贝出来的是一个可变数组,而且两个地址也不相同,为深拷贝。

集合类型 + 不可变类型 + copy

先上代码:

1
2
3
4
5
6
7
8
9
10
11
12
/**
集合类型 - 不可变类型 - imutableCopy
*/
- (void)setTypeImutableTypeImutableCopy
{
NSArray *imutableArr = [NSArray arrayWithObjects:@"first",@"second",@"third", nil];
NSArray *imutableArrImutableCopy = [imutableArr copy];
NSLog(@"%@",[[imutableArr copy] class]);
NSLog(@"%@ --- %p",imutableArr,imutableArr);
NSLog(@"%@ --- %p",imutableArrImutableCopy,imutableArrImutableCopy);
}

打印结果:

1
2
3
4
5
6
7
8
9
10
11
2018-04-05 11:04:59.656530+0800 CopyDemo[11202:404553] __NSArrayI
2018-04-05 11:04:59.656685+0800 CopyDemo[11202:404553] (
first,
second,
third
) --- 0x60000024c5a0
2018-04-05 11:04:59.656809+0800 CopyDemo[11202:404553] (
first,
second,
third
) --- 0x60000024c5a0

这个结果好像和之前的有点不同,两个地址是相同的,所以为浅拷贝,另外拷贝出来的是一个不可变数组。

集合类型总结

可变类型 不可变类型
mutableCopy 可变类型,深拷贝 可变类型,深拷贝
copy 不可变类型,深拷贝 不可变类型,浅拷贝

非集合类型的拷贝

非集合类型中的拷贝出来的类型结果好像有点不对,这里暂时没找出原因,如果有大佬看到希望能够帮忙指正一下。

非集合类型 + 可变类型

非集合类型 + 可变类型 + mutableCopy

先上代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#pragma mark -
#pragma mark - not set type
/**
非集合类型 - 可变类型 - mutableCopy
*/
- (void)unsetTypeMutableTypeMutableCopy
{
NSMutableString *mutableStr = [NSMutableString stringWithFormat:@"mutableStr"];
NSMutableString *mutableStrMutableCopy = [mutableStr mutableCopy];
NSLog(@"%@",[[mutableStr mutableCopy] class]);
NSLog(@"%@ --- %p",mutableStr,mutableStr);
NSLog(@"%@ --- %p",mutableStrMutableCopy,mutableStrMutableCopy);
}

打印结果:

1
2
3
2018-04-05 11:04:59.656967+0800 CopyDemo[11202:404553] __NSCFString
2018-04-05 11:04:59.657094+0800 CopyDemo[11202:404553] mutableStr --- 0x604000251190
2018-04-05 11:04:59.657223+0800 CopyDemo[11202:404553] mutableStr --- 0x604000251550

可以看到地址不同,深拷贝。

非集合类型 + 可变类型 + copy

先上代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
/**
非集合类型 - 可变类型 - imutableCopy
*/
- (void)unsetTypeMutableTypeImutableCopy
{
NSMutableString *mutableStr = [NSMutableString stringWithFormat:@"mutableStr"];
NSMutableString *mutableStrImutableCopy = [mutableStr copy];
NSLog(@"%@",[[mutableStr copy] class]);
NSLog(@"%@ --- %p",mutableStr,mutableStr);
NSLog(@"%@ --- %p",mutableStrImutableCopy,mutableStrImutableCopy);
}

打印结果:

1
2
3
2018-04-05 11:04:59.661548+0800 CopyDemo[11202:404553] __NSCFString
2018-04-05 11:04:59.661682+0800 CopyDemo[11202:404553] mutableStr --- 0x604000251550
2018-04-05 11:04:59.661850+0800 CopyDemo[11202:404553] mutableStr --- 0x60400003c580

可以看到地址不同,深拷贝。

非集合类型 + 不可变类型

非集合类型 + 不可变类型 + mutableCopy

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
/**
非集合类型 - 不可变类型 - mutableCopy
*/
- (void)unsetTypeImutableTypeMutableCopy
{
NSString *str = [NSString stringWithFormat:@"ImutableStr"];
NSString *strMutableCopy = [str mutableCopy];
NSLog(@"%@",[[str mutableCopy] class]);
NSLog(@"%@ --- %p",str,str);
NSLog(@"%@ --- %p",strMutableCopy,strMutableCopy);
}

打印结果:

1
2
3
2018-04-05 11:04:59.661968+0800 CopyDemo[11202:404553] __NSCFString
2018-04-05 11:04:59.662072+0800 CopyDemo[11202:404553] ImutableStr --- 0x60400003c580
2018-04-05 11:04:59.662184+0800 CopyDemo[11202:404553] ImutableStr --- 0x604000251340

同样是深拷贝,地址不同。

非集合类型 + 不可变类型 + copy

先上代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
/**
非集合类型 - 不可变类型 - imutableCopy
*/
- (void)unsetTypeImutableTypeImutableCopy
{
NSString *str = [NSString stringWithFormat:@"ImtableStr"];
NSString *strImutableCopy = [str copy];
NSLog(@"%@",[[str copy] class]);
NSLog(@"%@ --- %p",str,str);
NSLog(@"%@ --- %p",strImutableCopy,strImutableCopy);
}

打印结果:

1
2
3
2018-04-05 11:04:59.662703+0800 CopyDemo[11202:404553] __NSCFString
2018-04-05 11:04:59.663681+0800 CopyDemo[11202:404553] ImtableStr --- 0x600000237340
2018-04-05 11:04:59.663882+0800 CopyDemo[11202:404553] ImtableStr --- 0x600000237340

可以看到,地址是相同的,所以为浅拷贝。

非集合类型总结

所以,可以发现,集合与非集合类型的结果基本上相同,但是这里拷贝出的类型好像有点问题。

可变类型 不可变类型
mutableCopy 深拷贝 深拷贝
copy 深拷贝 浅拷贝

自定义类型

我们先自定义一个model,里边包括一个name,一个location。

注意这里要添加NSCopying和NSMutableCopying协议。

1
2
3
4
5
6
7
8
#import <Foundation/Foundation.h>
@interface CopyModel : NSObject<NSCopying,NSMutableCopying>
@property (nonatomic ,copy) NSString *name;
@property (nonatomic ,copy) NSString *location;
@end

然后还需要实现两个方法,否则在copy或mutableCopy时会发生崩溃。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#import "CopyModel.h"
@implementation CopyModel
- (instancetype)copyWithZone:(NSZone *)zone
{
CopyModel *copyModel = [[CopyModel alloc] init];
copyModel.name = self.name;
copyModel.location = self.location;
return copyModel;
}
- (instancetype)mutableCopyWithZone:(NSZone *)zone
{
CopyModel *copyModel = [[CopyModel alloc] init];
copyModel.name = self.name;
copyModel.location = self.location;
return copyModel;
}
@end

接下来我们来看一下自定义类型的copy和mutableCopy的结果。

自定义类型 + mutableCopy

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/**
自定义类型 - mutableCopy
*/
- (void)customTypeMutableCopy
{
CopyModel *model = [[CopyModel alloc] init];
model.name = @"name";
model.location = @"location";
CopyModel *copyModel = [model mutableCopy];
NSLog(@"%@ --- %@ --- %p",model.name,model.location,model);
NSLog(@"%@ --- %@ --- %p",copyModel.name,copyModel.location,copyModel);
}

打印结果:

1
2
2018-04-05 11:04:59.664868+0800 CopyDemo[11202:404553] name --- location --- 0x60400003c400
2018-04-05 11:04:59.665014+0800 CopyDemo[11202:404553] name --- location --- 0x60400003c320

可以看到地址不同,是深拷贝。

自定义类型 + copy

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/**
自定义类型 - ImutableCopy
*/
- (void)customTypeImutableCopy
{
CopyModel *model = [[CopyModel alloc] init];
model.name = @"name";
model.location = @"location";
CopyModel *copyModel = [model copy];
NSLog(@"%@ --- %@ --- %p",model.name,model.location,model);
NSLog(@"%@ --- %@ --- %p",copyModel.name,copyModel.location,copyModel);
}

打印结果:

1
2
2018-04-05 11:04:59.665165+0800 CopyDemo[11202:404553] name --- location --- 0x60400003c400
2018-04-05 11:04:59.665404+0800 CopyDemo[11202:404553] name --- location --- 0x60400003c320

可以看到地址不同,是深拷贝。

@property 中的copy

属性的常用修饰符里,就有一个就是copy,那这个copy和其他的修饰符会有什么区别呢?

copy vs strong

一般在修饰NSString属性时,通常会用copy,那如果我们不用copy,而改用strong会发生什么问题呢??

先上代码:

1
2
@property (nonatomic ,strong) NSString *myStrongStr;
@property (nonatomic ,copy) NSString *myCopyStr;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
- (void)strongStrVSCopyStr
{
NSMutableString *otherStr = [NSMutableString stringWithFormat:@"otherStr"];
self.myStrongStr = otherStr;
self.myCopyStr = otherStr;
[otherStr appendString:@"addSomeThing"];
NSLog(@"%@ --- %p",self.myStrongStr,self.myStrongStr);
NSLog(@"%@ --- %p",self.myCopyStr,self.myCopyStr);
NSLog(@"%@ --- %p",otherStr,otherStr);
}

打印结果:

1
2
3
2018-04-05 11:34:04.034151+0800 CopyDemo[11944:480031] otherStraddSomeThing --- 0x600000241170
2018-04-05 11:34:04.034339+0800 CopyDemo[11944:480031] otherStr --- 0xa000c45401541058
2018-04-05 11:34:04.034619+0800 CopyDemo[11944:480031] otherStraddSomeThing --- 0x600000241170

结果应该很明显了,我并没有对myStrongStr进行操作,但是他的结果却变了,而且他与otherStr的地址也是相同的,那这是为什么呢?

是因为copy修饰的属性setter方法,调用时会先release旧值,copy新值再赋值给成员变量,不可变copy是深拷贝,地址就变化了。

而strong修饰之后只是强指针引用,并没有改变地址,所以myStrongStr会随着otherStr的值进行变化。他们的地址也是相同的。

copy vs retain

在MRC下进行测试:

1
2
3
4
5
6
7
8
9
10
11
12
- (void)strongVSretain {
NSMutableArray *arrM = [NSMutableArray arrayWithObjects:@"111",@"222",@"333", nil];
NSMutableArray *arrMRetain = [arrM retain];
NSMutableArray *arrMCopy = [arrM copy];
[arrM removeLastObject];
NSLog(@"arrMCopy--%@--%p--%lu",arrMCopy,arrMCopy,[arrMCopy retainCount]);
NSLog(@"arrMRetain--%@--%p--%lu",arrMRetain,arrMRetain,[arrMRetain retainCount]);
}

打印结果:

1
2
3
4
5
6
7
8
9
2018-04-05 11:34:04.034619+0800 CopyDemo[11944:480031] arrMCopy--(
111,
222,
333
)--0x60000005cf80--1
2018-04-05 11:34:04.034619+0800 CopyDemo[11944:480031] arrMRetain--(
111,
222
)--0x60000005cf50--2

可以看到,copy是深拷贝,retainCount为1,retain为浅拷贝,retain是使原来的引用计数+1,所以两个数组时同一个地址,改变的话也会一起改变。

可变类型使用copy修饰

当我们创建一个可变类型的变量而使用copy修饰的时候,可能会发生一个问题,就是使用可变类型的方法时,就会发生崩溃。

比如:

1
@property (nonatomic ,copy) NSMutableArray *myMutableArr;
1
2
self.myMutableArr = [NSMutableArray arrayWithObjects:@"first",@"second",@"third", nil];
[self.myMutableArr addObject:@"forth"];

这段代码运行时就会发生崩溃,报错信息为:

1
2018-04-05 11:42:52.239877+0800 CopyDemo[12135:500454] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[__NSArrayI addObject:]: unrecognized selector sent to instance 0x600000250200'

我们发现这个报错的原因为不可变数组(__NSArrayI)找不到addObject:的方法。那是为什么呢?

因为我们之前也说过,在使用copy修饰符的时候,是先release掉旧值,再copy出一个新的值,而在前边的内容中也有说到,可变数组copy出来的是一个不可变数组,所以实际上这时候myMutableArr就是一个不可变数组了,这时候给他发送可变数组的方法,他当然就会崩溃了。

最后

最后总结一下,可以看到,只有在不可变类型的copy时,才会发生浅拷贝,而其他所有的情况下都为深拷贝,并且一般来说无论是可变还是不可变类型的变量,mutableCopy后都会变成可变类型,copy后都会变成不可变类型。

最后Demo在这里

祝大家清明节快乐~~

参考文档

Copying Collections

iOS 集合的深复制与浅复制

ios中的拷贝你知道多少?