ios中的拷贝你知道多少?

简述深浅拷贝

我们实例化的对象存储在堆区,而指向对象的指针一般存储在栈区。我们需要知道这个前提。
  实际上拷贝分为深拷贝(one level deep copy),浅拷贝(shallow copy)和完全拷贝(real deep copy)三种。

  1. 浅拷贝:在操作中,对于被复制对象的每一层都是指针复制。
  2. 深拷贝:在操作中,对于被复制对象,至少有一层是深复制。
  3. 完全拷贝:在操作中,对于被复制对象,每一层都是对象复制。

通过下图我们来看深浅拷贝。

浅拷贝
深拷贝

通过上面两个图我们可以这么认为,浅拷贝就是指针层面的赋值,指针1复制为指针2,它们指向了同一个对象;深拷贝是指针和对象的全拷贝,指针1拷贝为指针2,对象1拷贝为对象2,对象1和对象2是独立的占用不同的地址。下面我们还可以看这张我在官方文档上借用的图,也可以说明我上面写的问题。

深拷贝和浅拷贝

下面我们对深拷贝、浅拷贝和完全拷贝进行分析归纳和测试。

详述深浅拷贝

在深入研究深浅拷贝之前,我们需要载体,这里我们以非可变对象(NSArray、NSString、NSDictionary)和可变对象(NSMutableArray、NSMutableString、NSMutableDictionary)以及自定义对象进行研究测试。分别研究它们的copy和mutableCopy(这里假设阅读者已经知道copy和mutableCopy了,不再赘述),并研究它们的引用计数。

一、NSString 和 NSMutableString的拷贝

● ARC下的拷贝

1. NSString的copy和mutableCopy
不多说别的,直接上代码。

- (void)viewDidLoad {
    [super viewDidLoad];

    NSString *str = @"虫儿不会飞";
    NSString *strCopy = [str copy];
    NSMutableString *strMutableCopy = [str mutableCopy];
    NSLog(@"str---%@---%p----%@----",str,str,[str class]);
    NSLog(@"strCopy---%@---%p----%@----",strCopy,strCopy,[strCopy class]);
    NSLog(@"strMutableCopy---%@---%p----%@----",strMutableCopy,strMutableCopy,[strMutableCopy class]);
}

看打印输出结果

2017-03-11 14:07:47.692 test111[5122:374020] str---虫儿不会飞---0x10b8fe078----__NSCFConstantString----
2017-03-11 14:07:47.692 test111[5122:374020] strCopy---虫儿不会飞---0x10b8fe078----__NSCFConstantString----
2017-03-11 14:07:47.693 test111[5122:374020] strMutableCopy---虫儿不会飞---0x60800026b540----__NSCFString----

结论:不可变字符串NSString,它的copy出来的对象地址和原对象一样是浅拷贝,而mutableCopy后的对象地址和原对象地址不一样,是深拷贝。

2. NSMutableString的copy和mutableCopy

- (void)viewDidLoad {
    [super viewDidLoad];

    NSMutableString *str =[NSMutableString stringWithString:@"虫儿不会飞"];
    NSString *strCopy = [str copy];
    NSMutableString *strMutableCopy = [str mutableCopy];
    NSLog(@"str---%@---%p----%@----",str,str,[str class]);
    NSLog(@"strCopy---%@---%p----%@----",strCopy,strCopy,[strCopy class]);
    NSLog(@"strMutableCopy---%@---%p----%@----",strMutableCopy,strMutableCopy,[strMutableCopy class]);
}

看打印输出结果

2017-03-11 23:34:22.591 test111[6244:450630] str---虫儿不会飞---0x6000000783c0----__NSCFString----
2017-03-11 23:34:22.591 test111[6244:450630] strCopy---虫儿不会飞---0x60000005d370----__NSCFString----
2017-03-11 23:34:22.591 test111[6244:450630] strMutableCopy---虫儿不会飞---0x6000000786c0----__NSCFString----

结论:变字符串NSMutableString,它的copy和mutableCopy出来的对象地址和原对象地址都不是一样的,是深拷贝。

● MRC下的拷贝

在开始测试之前,我们需要先将工程设置为MRC模式,设置方法不需多说,基本都会了吧,我就直接上图了。

ARC 转为 MRC.png

1. NSString的copy和mutableCopy

// MRC下NSString 拷贝
- (void)viewDidLoad {
    [super viewDidLoad];
    
    NSString * str = @"虫儿不会飞";
    NSLog(@"str--%@--%p--%lu--%@",str,str,[str retainCount],[str class]);
    
    NSString *strCopy = [str copy];
    [strCopy retain];
    NSLog(@"strCopy--%@--%p--%lu--%@",strCopy,strCopy,[strCopy retainCount],[strCopy class]);
    
    NSMutableString *strMCopy = [str mutableCopy];
    [strMCopy retain];
    NSLog(@"strMCopy--%@--%p--%lu--%@",strMCopy,strMCopy,[strMCopy retainCount],[strMCopy class]);

}

看结果

2017-03-13 21:22:12.676 MRC拷贝研究[2609:156555] str--虫儿不会飞--0x106a8e050--18446744073709551615--__NSCFConstantString
2017-03-13 21:22:12.676 MRC拷贝研究[2609:156555] strCopy--虫儿不会飞--0x106a8e050--18446744073709551615--__NSCFConstantString
2017-03-13 21:22:12.677 MRC拷贝研究[2609:156555] strMCopy--虫儿不会飞--0x608000070a80--2--__NSCFString

结论:不可变字符串NSString,它的原对象和拷贝对象地址相同copy是浅拷贝,mutableCopy是深拷贝;引用计数,copy以后retainCount近似无穷大的数,所以不用管理它的释放,mutableCopy后的对象计数为1,retain在加1,最后为2。

2. NSMutableString的copy和mutableCopy

// MRC下NSMutableString 拷贝
- (void)viewDidLoad {
    [super viewDidLoad];
    
    NSString * strI = @"虫儿不会飞";
    
    NSMutableString * str = [NSMutableString stringWithString:strI];
    NSLog(@"str--%@--%p--%lu--%@",str,str,[str retainCount],[str class]);
    
    NSString *strCopy = [str copy];
    [strCopy retain];
    NSLog(@"strCopy--%@--%p--%lu--%@",strCopy,strCopy,[strCopy retainCount],[strCopy class]);
    
    NSMutableString *strMCopy = [str mutableCopy];
    [strMCopy retain];
    NSLog(@"strMCopy--%@--%p--%lu--%@",strMCopy,strMCopy,[strMCopy retainCount],[strMCopy class]);
    
}

看结果

2017-03-13 21:36:25.763 MRC拷贝研究[2831:176161] str--虫儿不会飞--0x600000070000--1--__NSCFString
2017-03-13 21:36:25.763 MRC拷贝研究[2831:176161] strCopy--虫儿不会飞--0x608000242c70--2--__NSCFString
2017-03-13 21:36:25.764 MRC拷贝研究[2831:176161] strMCopy--虫儿不会飞--0x60000006f740--2--__NSCFString

结论:可变字符串NSMutableString,它的copy和mutableCopy均是深拷贝;引用计数,copy和mutableCopy后引用计数为1,retain在加1,最后为2。


二、NSArray 和 NSMutableArray的拷贝

● ARC下的拷贝

1. NSArray的copy和mutableCopy

- (void)viewDidLoad {
    [super viewDidLoad];
    
    NSArray *strArr = @[@"111", @"222"];
    NSArray *strArrCopy = [strArr copy];
    NSMutableArray *strArrMutableCopy = [strArr mutableCopy];
    NSLog(@"str---%@---%p----%@----",strArr,strArr,[strArr class]);
    NSLog(@"strCopy---%@---%p----%@----",strArrCopy,strArrCopy,[strArrCopy class]);
    NSLog(@"strMutableCopy---%@---%p----%@----",strArrMutableCopy,strArrMutableCopy,[strArrMutableCopy class]);
}

直接看结果

2017-03-11 23:46:17.145 test111[6436:467460] str---(
    111,
    222
)---0x60800002ba80----__NSArrayI----
2017-03-11 23:46:17.147 test111[6436:467460] strCopy---(
    111,
    222
)---0x60800002ba80----__NSArrayI----
2017-03-11 23:46:17.147 test111[6436:467460] strMutableCopy---(
    111,
    222
)---0x6080002441d0----__NSArrayM----

结论:不可变数组NSArray,它的copy所得对象地址和原对象地址相同,是浅拷贝。而mutableCopy后的对象地址和原对象地址不一样,是深拷贝。

2. NSMutableArray的copy和mutableCopy

- (void)viewDidLoad {
    [super viewDidLoad];
    
    NSArray *strArr1 = @[@"111", @"222"];
    NSMutableArray *strArrM = [NSMutableArray arrayWithArray:strArr1];
    NSArray *strArrMCopy = [strArrM copy];
    NSMutableArray *strArrMMutableCopy = [strArrM mutableCopy];
    NSLog(@"str---%@---%p----%@----",strArrM,strArrM,[strArrM class]);
    NSLog(@"strCopy---%@---%p----%@----",strArrMCopy,strArrMCopy,[strArrMCopy class]);
    NSLog(@"strMutableCopy---%@---%p----%@----",strArrMMutableCopy,strArrMMutableCopy,[strArrMMutableCopy class]);
}

直接查看结果

2017-03-12 00:08:00.172 test111[6741:494893] str---(
    111,
    222
)---0x600000056b90----__NSArrayM----
2017-03-12 00:08:00.173 test111[6741:494893] strCopy---(
    111,
    222
)---0x600000039f00----__NSArrayI----
2017-03-12 00:08:00.173 test111[6741:494893] strMutableCopy---(
    111,
    222
)---0x600000056b30----__NSArrayM----

结论:可变数组NSMutableArray,它的copy和mutableCopy所得对象地址和原对象地址都不相同,是深拷贝。

● MRC下的拷贝

1. NSArray的copy和mutableCopy

// MRC下NSArray 拷贝
- (void)viewDidLoad {
    [super viewDidLoad];
    
    NSArray * arrI = @[@"lalalalala"];
    NSLog(@"arrI--%@--%p--%lu--%@",arrI,arrI,[arrI retainCount],[arrI class]);
    
    NSArray *arrICopy = [arrI copy];
    [arrICopy retain];
    NSLog(@"arrICopy--%@--%p--%lu--%@",arrICopy,arrICopy,[arrICopy retainCount],[arrICopy class]);
    
    NSMutableArray *arrMCopy = [arrI mutableCopy];
    [arrMCopy retain];
    NSLog(@"arrMCopy--%@--%p--%lu--%@",arrMCopy,arrMCopy,[arrMCopy retainCount],[arrMCopy class]);
}

看结果

2017-03-13 21:45:34.180 MRC拷贝研究[2999:188861] arrI--(
   lalalalala
)--0x608000003510--1--__NSSingleObjectArrayI
2017-03-13 21:45:34.181 MRC拷贝研究[2999:188861] arrICopy--(
   lalalalala
)--0x608000003510--3--__NSSingleObjectArrayI
2017-03-13 21:45:34.182 MRC拷贝研究[2999:188861] arrMCopy--(
   lalalalala
)--0x608000048be0--2--__NSArrayM

结论:NSArray copy后为浅拷贝,mutableCopy后为深拷贝。但是需要注意,浅拷贝对象计数在原有基础上+1 ,为2,retain后再+1,为3。

2. NSMutableArray的copy和mutableCopy


// MRC下NSMutableArray 拷贝
- (void)viewDidLoad {
    [super viewDidLoad];
    
    NSArray * arrI = @[@"lalalalala"];
    
    NSMutableArray *arrM = [NSMutableArray arrayWithArray:arrI];
    NSLog(@"arrI--%@--%p--%lu--%@",arrM,arrM,[arrM retainCount],[arrM class]);
    
    NSArray *arrCopy = [arrM copy];
    [arrCopy retain];
    NSLog(@"arrICopy--%@--%p--%lu--%@",arrCopy,arrCopy,[arrCopy retainCount],[arrCopy class]);
    
    NSMutableArray *arrMCopy = [arrM mutableCopy];
    [arrMCopy retain];
    NSLog(@"arrMCopy--%@--%p--%lu--%@",arrMCopy,arrMCopy,[arrMCopy retainCount],[arrMCopy class]);
}

看结果

2017-03-13 22:10:52.515 MRC拷贝研究[3394:225252] arrI--(
    lalalalala
)--0x600000051940--1--__NSArrayM
2017-03-13 22:10:52.515 MRC拷贝研究[3394:225252] arrICopy--(
    lalalalala
)--0x60000000d030--2--__NSSingleObjectArrayI
2017-03-13 22:10:52.516 MRC拷贝研究[3394:225252] arrMCopy--(
    lalalalala
)--0x600000051850--2--__NSArrayM

结论:可变数组NSMutableArray的copy 和 mutableCopy均为深拷贝,copy 和 mutableCopy后对象的计数为1,retain+1,为2。


三、NSDictionary 和 NSMutableDictionary的拷贝

● ARC下的拷贝

1. NSDictionary的copy和mutableCopy

- (void)viewDidLoad {
    [super viewDidLoad];
    
    NSDictionary *strDict = @{@"111":@"222"};
    NSDictionary *strDictCopy = [strDict copy];
    NSMutableDictionary *strDictMutableCopy = [strDict mutableCopy];
    NSLog(@"str---%@---%p----%@----",strDict,strDict,[strDict class]);
    NSLog(@"strCopy---%@---%p----%@----",strDictCopy,strDictCopy,[strDictCopy class]);
    NSLog(@"strMutableCopy---%@---%p----%@----",strDictMutableCopy,strDictMutableCopy,[strDictMutableCopy class]);
}

直接看结果

2017-03-12 08:55:19.709 test111[1138:43171] str---{
    111 = 222;
}---0x608000037b00----__NSSingleEntryDictionaryI----
2017-03-12 08:55:19.710 test111[1138:43171] strCopy---{
    111 = 222;
}---0x608000037b00----__NSSingleEntryDictionaryI----
2017-03-12 08:55:19.710 test111[1138:43171] strMutableCopy---{
    111 = 222;
}---0x608000059740----__NSDictionaryM----

结论:不可变字典NSDictionary,它的copy所得对象地址和原对象地址相同,是浅拷贝。而mutableCopy后的对象地址和原对象地址不一样,是深拷贝。

2. NSMutableDictionary的copy和mutableCopy

- (void)viewDidLoad {
    [super viewDidLoad];
    
    NSDictionary *strDict = @{@"111":@"222"};
    NSMutableDictionary *strDictM = [NSMutableDictionary dictionaryWithDictionary:strDict];
    NSDictionary *strDictMCopy = [strDictM copy];
    NSMutableDictionary *strDictMutableCopy = [strDictM mutableCopy];
    NSLog(@"str---%@---%p----%@----",strDictM,strDictM,[strDictM class]);
    NSLog(@"strCopy---%@---%p----%@----",strDictMCopy,strDictMCopy,[strDictMCopy class]);
    NSLog(@"strMutableCopy---%@---%p----%@----",strDictMutableCopy,strDictMutableCopy,[strDictMutableCopy class]);
}

直接看结果

2017-03-12 08:59:03.704 test111[1201:48465] str---{
    111 = 222;
}---0x60800005e870----__NSDictionaryM----
2017-03-12 08:59:03.705 test111[1201:48465] strCopy---{
    111 = 222;
}---0x608000073f80----__NSDictionaryI----
2017-03-12 08:59:03.705 test111[1201:48465] strMutableCopy---{
    111 = 222;
}---0x60800005e600----__NSDictionaryM----

结论:可变字典NSMutableDictionary,它的copy和mutableCopy出来的对象地址和原对象地址都不是一样的,是深拷贝。

● MRC下的拷贝

1. NSDictionary的copy和mutableCopy

// MRC下NSDictionary 拷贝
- (void)viewDidLoad {
    [super viewDidLoad];
    
    NSDictionary * dictI = @{@"lalalalala":@"heiheiheihei"};
    NSLog(@"dictI--%@--%p--%lu--%@",dictI,dictI,[dictI retainCount],[dictI class]);
    
    NSDictionary *dictICopy = [dictI copy];
    [dictICopy retain];
    NSLog(@"dictICopy--%@--%p--%lu--%@",dictICopy,dictICopy,[dictICopy retainCount],[dictICopy class]);
    
    NSMutableDictionary *dictMCopy = [dictI mutableCopy];
    [dictMCopy retain];
    NSLog(@"dictMCopy--%@--%p--%lu--%@",dictMCopy,dictMCopy,[dictMCopy retainCount],[dictMCopy class]);
}

看结果

2017-03-13 22:22:26.809 MRC拷贝研究[3589:242671] dictI--{
    lalalalala = heiheiheihei;
}--0x60000002ac00--1--__NSSingleEntryDictionaryI
2017-03-13 22:22:26.810 MRC拷贝研究[3589:242671] dictICopy--{
    lalalalala = heiheiheihei;
}--0x60000002ac00--3--__NSSingleEntryDictionaryI
2017-03-13 22:22:26.810 MRC拷贝研究[3589:242671] dictMCopy--{
    lalalalala = heiheiheihei;
}--0x60800004e5e0--2--__NSDictionaryM

结论:NSDictionary的copy为浅拷贝,mutableCopy为深拷贝,浅拷贝后再retain,最后dictICopy的引用计数为3。

2. NSMutableDictionary的copy和mutableCopy

// MRC下NSMutableDictionary 拷贝
- (void)viewDidLoad {
    [super viewDidLoad];
    
    NSDictionary * dict = @{@"lalalalala":@"heiheiheihei"};
    
    NSMutableDictionary *dictM = [NSMutableDictionary dictionaryWithDictionary:dict];
    NSLog(@"dictM--%@--%p--%lu--%@",dictM,dictM,[dictM retainCount],[dictM class]);
    
    NSDictionary *dictCopy = [dictM copy];
    [dictCopy retain];
    NSLog(@"dictCopy--%@--%p--%lu--%@",dictCopy,dictCopy,[dictCopy retainCount],[dictCopy class]);
    
    NSMutableDictionary *dictMCopy = [dictM mutableCopy];
    [dictMCopy retain];
    NSLog(@"dictMCopy--%@--%p--%lu--%@",dictMCopy,dictMCopy,[dictMCopy retainCount],[dictMCopy class]);
}

看结果

2017-03-13 22:36:01.475 MRC拷贝研究[3787:261783] dictM--{
    lalalalala = heiheiheihei;
}--0x6000000529c0--1--__NSDictionaryM
2017-03-13 22:36:01.475 MRC拷贝研究[3787:261783] dictCopy--{
    lalalalala = heiheiheihei;
}--0x608000267e80--2--__NSDictionaryI
2017-03-13 22:36:01.476 MRC拷贝研究[3787:261783] dictMCopy--{
    lalalalala = heiheiheihei;
}--0x6000000529f0--2--__NSDictionaryM

结论:可变字典的copy和mutableCopy均为深拷贝,深拷贝后对象引用计数为1,retain后为2。


四、自定义对象的拷贝

● ARC下的拷贝

我们先自定义一个对象,先看这个工程框架,为了看着方便,工程名字我就用汉字了,不规范,大家不要学,这里只是为了好识别而已。

自定义对象文件结构.png

我们自定义了一个类 DDCity,下面我们看一下工程里的文件我都在里面写了什么。
DDCity.h文件

#import <Foundation/Foundation.h>

@interface DDCity : NSObject

@property (nonatomic,copy) NSString * cityName;
@property (nonatomic,copy) NSString * cityLocation;

@end

ViewController.m文件

#import "ViewController.h"
#import "DDCity.h"

@interface ViewController ()

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];

    DDCity *city = [[DDCity alloc] init];
    city.cityName = @"北京";
    city.cityLocation = @"中国";

    DDCity *cityCopy = [city copy];
    DDCity *cityMCopy = [city mutableCopy];
    
    NSLog(@"city---%@---%@",city.cityName,city.cityLocation);
    NSLog(@"cityCopy---%@---%@",cityCopy.cityName,cityCopy.cityLocation);
    NSLog(@"cityMCopy---%@---%@",cityMCopy.cityName,cityMCopy.cityLocation);

    NSLog(@"city---%@---%p---%@",city,city,[city class]);
    NSLog(@"cityCopy---%@---%p---%@",cityCopy,cityCopy,[cityCopy class]);
    NSLog(@"cityMCopy---%@---%p---%@",cityMCopy,cityMCopy,[cityMCopy class]);
}

运行看结果crash,看提示信息

2017-03-12 10:54:44.930 自定义对象copy[2428:129638] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[DDCity copyWithZone:]: unrecognized selector sent to instance 0x6000000276e0'
*** First throw call stack:
(
    0   CoreFoundation                      0x000000010d3ffd4b __exceptionPreprocess + 171
    1   libobjc.A.dylib                     0x000000010ce6121e objc_exception_throw + 48
    2   CoreFoundation                      0x000000010d46ff04 -[NSObject(NSObject) doesNotRecognizeSelector:] + 132

从提示上我们看见是没有实现copyWithZone 方法,这里我们就在DDCity.m中实现对应的copyWithZone方法。

在DDCity.h文件中增加协议
@interface DDCity : NSObject <NSCopying, NSMutableCopying>

在DDCity.m中实现两个协议方法

- (instancetype) copyWithZone:(NSZone *)zone {
    DDCity *city = [[DDCity allocWithZone:zone] init];
    NSLog(@"没有我copyWithZone你自定义对象就不能copy");
    return city;
}
- (instancetype) mutableCopyWithZone:(NSZone *)zone {
    DDCity *city = [[DDCity allocWithZone:zone] init];
    NSLog(@"没有我mutableCopyWithZone你自定义对象就不能MCopy");
    return city;
}

viewController中的代码不动,再次运行看结果

2017-03-12 11:32:52.068 自定义对象copy[3039:180702] 没有我copyWithZone你自定义对象就不能copy
2017-03-12 11:32:52.068 自定义对象copy[3039:180702] 没有我mutableCopyWithZone你自定义对象就不能MCopy
2017-03-12 11:32:52.069 自定义对象copy[3039:180702] city---北京---中国
2017-03-12 11:32:52.069 自定义对象copy[3039:180702] cityCopy---(null)---(null)
2017-03-12 11:32:52.069 自定义对象copy[3039:180702] cityMCopy---(null)---(null)
2017-03-12 11:32:52.070 自定义对象copy[3039:180702] city---<DDCity: 0x600000037020>---0x600000037020---DDCity
2017-03-12 11:32:52.070 自定义对象copy[3039:180702] cityCopy---<DDCity: 0x600000037080>---0x600000037080---DDCity
2017-03-12 11:32:52.071 自定义对象copy[3039:180702] cityMCopy---<DDCity: 0x6000000370a0>---0x6000000370a0---DDCity

结论:1)自定义对象copy和mutableCopy后的对象地址都不一样,均为深拷贝。2)拷贝后的对象属性cityName和cityLocation均为null,也就是说属性并未拷贝,我们再次改进DDCity.m中的代码。

- (instancetype) copyWithZone:(NSZone *)zone {
    DDCity *city = [[DDCity allocWithZone:zone] init];
    //新增下面两行代码 
    city.cityName = self.cityName;
    city.cityLocation = self.cityLocation;
    
    NSLog(@"没有我copyWithZone你自定义对象就不能copy");
    return city;
}

- (instancetype) mutableCopyWithZone:(NSZone *)zone {
    DDCity *city = [[DDCity allocWithZone:zone] init];
    //新增下面两行代码 
    city.cityName = self.cityName;
    city.cityLocation = self.cityLocation;
    
    NSLog(@"没有我mutableCopyWithZone你自定义对象就不能MCopy");
    return city;
}

查看结果

2017-03-12 11:49:20.900 自定义对象copy[3304:202108] 没有我copyWithZone你自定义对象就不能copy
2017-03-12 11:49:20.901 自定义对象copy[3304:202108] 没有我mutableCopyWithZone你自定义对象就不能MCopy
2017-03-12 11:49:20.902 自定义对象copy[3304:202108] city---北京---中国
2017-03-12 11:49:20.902 自定义对象copy[3304:202108] cityCopy---北京---中国
2017-03-12 11:49:20.902 自定义对象copy[3304:202108] cityMCopy---北京---中国
2017-03-12 11:49:20.903 自定义对象copy[3304:202108] city---<DDCity: 0x608000036d60>---0x608000036d60---DDCity
2017-03-12 11:49:20.903 自定义对象copy[3304:202108] cityCopy---<DDCity: 0x608000036da0>---0x608000036da0---DDCity
2017-03-12 11:49:20.904 自定义对象copy[3304:202108] cityMCopy---<DDCity: 0x600000037560>---0x600000037560---DDCity

结论:通过增加对属性的赋值,新拷贝的对象就拥有了原对象的属性值。

● MRC下的拷贝

- (void)viewDidLoad {
    [super viewDidLoad];

    DDCity *city = [[DDCity alloc] init];
    city.cityName = @"北京";
    city.cityLocation = @"中国";

    DDCity *cityCopy = [city copy];
    [cityCopy retain];
    
    DDCity *cityMCopy = [city mutableCopy];
    [cityMCopy retain];
    
    NSLog(@"city---%@---%@",city.cityName,city.cityLocation);
    NSLog(@"cityCopy---%@---%@",cityCopy.cityName,cityCopy.cityLocation);
    NSLog(@"cityMCopy---%@---%@",cityMCopy.cityName,cityMCopy.cityLocation);

    NSLog(@"city---%@---%p---%@",city,city,[city class]);
    NSLog(@"cityCopy---%@---%p---%@",cityCopy,cityCopy,[cityCopy class]);
    NSLog(@"cityMCopy---%@---%p---%@",cityMCopy,cityMCopy,[cityMCopy class]);
    
    NSLog(@"city--%lu---%lu---%lu",city.retainCount,cityCopy.retainCount,cityMCopy.retainCount);
    NSLog(@"city--%lu---%lu---%lu",city.cityName.retainCount,cityCopy.cityName.retainCount,cityMCopy.cityName.retainCount);
    NSLog(@"city--%lu---%lu---%lu",city.cityLocation.retainCount,cityCopy.cityLocation.retainCount,cityMCopy.cityLocation.retainCount);

}

查看结果

2017-03-13 23:08:28.179 自定义对象copy[4284:303058] 没有我copyWithZone你自定义对象就不能copy
2017-03-13 23:08:28.180 自定义对象copy[4284:303058] 没有我mutableCopyWithZone你自定义对象就不能MCopy
2017-03-13 23:08:28.180 自定义对象copy[4284:303058] city---北京---中国
2017-03-13 23:08:28.180 自定义对象copy[4284:303058] cityCopy---北京---中国
2017-03-13 23:08:28.181 自定义对象copy[4284:303058] cityMCopy---北京---中国
2017-03-13 23:08:28.181 自定义对象copy[4284:303058] city---<DDCity: 0x60000002ab00>---0x60000002ab00---DDCity
2017-03-13 23:08:28.182 自定义对象copy[4284:303058] cityCopy---<DDCity: 0x60000002ab60>---0x60000002ab60---DDCity
2017-03-13 23:08:28.182 自定义对象copy[4284:303058] cityMCopy---<DDCity: 0x608000028ec0>---0x608000028ec0---DDCity
2017-03-13 23:08:28.182 自定义对象copy[4284:303058] city--1---2---2
2017-03-13 23:08:28.183 自定义对象copy[4284:303058] city--18446744073709551615---18446744073709551615---18446744073709551615
2017-03-13 23:08:28.183 自定义对象copy[4284:303058] city--18446744073709551615---18446744073709551615---18446744073709551615

结论:copy和mutableCopy均为深拷贝,拷贝后引用计数为1,retain后再+1,为2。


五、一些需要注意的点

理解深拷贝和完全拷贝

深复制,就是把原有对象内容直接克隆一份新的对象,但是这里有一个坑,就是深复制只是复制一层对象,而不是复制第二层或者更深层的对象。可能说的有点不好理解,下面看这个例子。

- (void)viewDidLoad {
    [super viewDidLoad];
    
    NSMutableString *strM1 = [NSMutableString stringWithString:@"1"];
    NSMutableString *strM2 = [NSMutableString stringWithString:@"2"];
    
    NSMutableArray *arrM1 = [NSMutableArray arrayWithObjects:strM1,strM2, nil];
    
    NSMutableString *strM3 = [arrM1 objectAtIndex:0];
    [strM3 appendString:@"1"];
    
    NSMutableArray *arrM2 = [arrM1 mutableCopy];
    
    NSLog(@"strM1--%@",arrM1);
    NSLog(@"strM2--%@",arrM2);
}

看结果

2017-03-14 00:22:02.260 深复制和完全复制[5522:393850] strM1--(
    11,
    2
)
2017-03-14 00:22:02.261 深复制和完全复制[5522:393850] strM2--(
    11,
    2
)

结果:大家可能会想,为什么深拷贝已经复制了对象,那么原对象为什么也跟着变?这里就是深拷贝和完全拷贝的原因,深拷贝只是拷贝了一层数组,但是里面的字符串没有拷贝,两个数组都是用的同一个地址的字符串,所以改变一个,原对象也发生了变化。可以做下面这样的修改。

NSMutableArray *arrM2 = [[NSMutableArray alloc] initWithArray:arrM1 copyItems:YES];

查看结果

2017-03-14 00:45:45.389 深复制和完全复制[5916:425450] arrM1--(
    11,
    2
)
2017-03-14 00:45:45.390 深复制和完全复制[5916:425450] arrM2--(
    1,
    2
)

可以利用这个方法,得到的是多一层的深复制,里面的字符串地址也进行了复制,所以改变strM3的值,不影响arrM1的值。你认为这样就解决了吗?在看下面的问题。

- (void)viewDidLoad {
    [super viewDidLoad];
    
    NSMutableString *strM1 = [NSMutableString stringWithString:@"1"];
    NSMutableString *strM2 = [NSMutableString stringWithString:@"2"];
    
    NSMutableArray *arrM1 = [NSMutableArray arrayWithObjects:strM1,strM2, nil];
    NSMutableArray *arrM2 = [NSMutableArray arrayWithObjects:strM1,strM2,arrM1, nil];
    
    NSMutableArray *arrM3 = [[NSMutableArray alloc] initWithArray:arrM2 copyItems:YES];
    
    NSMutableString *strM3 = [arrM1 objectAtIndex:0];
    [strM3 appendString:@"1"];
    
    NSLog(@"arrM2--%@",arrM2);
    NSLog(@"arrM3--%@",arrM3);
}

查看结果

2017-03-14 00:55:57.604 深复制和完全复制[6080:438490] arrM2--(
    11,
    2,
        (
        11,
        2
    )
)
2017-03-14 00:55:57.606 深复制和完全复制[6080:438490] arrM3--(
    1,
    2,
        (
        11,
        2
    )
)

结论:看这个结果,可以发现外层的深复制了,原对象和拷贝后的对象不是同一地址,再往里看一层都变化了,就没有深复制,也就是说在增加一层,NSMutableArray *arrM2 = [[NSMutableArray alloc] initWithArray:arrM1 copyItems:YES];这个方法不能管那么多层数了。采用归档和解档可以解决这个问题。

NSMutableArray *arrM3 = [NSKeyedUnarchiver unarchiveObjectWithData:[NSKeyedArchiver archivedDataWithRootObject:arrM2]];

查看结果

2017-03-14 01:17:23.204 深复制和完全复制[6396:464958] arrM2--(
    11,
    2,
        (
        11,
        2
    )
)
2017-03-14 01:17:23.204 深复制和完全复制[6396:464958] arrM3--(
    1,
    2,
        (
        1,
        2
    )
)

结论:可以看到实现了完全复制,就没有层数的限制了。


理解字符串NSString的copy和strong的不同

NSString被copy和strong修饰有什么不同,不多说废话,直接上代码了。

#import "ViewController.h"

@interface ViewController ()

@property (nonatomic,copy) NSString *str;

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    NSMutableString *strM = [[NSMutableString alloc] initWithString:@"bestDay"];
    self.str = strM;
    [strM appendString:@"OfThisYear"];
    NSLog(@"str----%@---%p",self.str,self.str);
    NSLog(@"strM----%@---%p",strM,strM);

}

@end

直接查看结果

2017-03-14 08:53:01.874 strong修饰不同[789:18128] str----bestDay---0xa796144747365627
2017-03-14 08:53:01.875 strong修饰不同[789:18128] strM----bestDayOfThisYear---0x600000071280

结论:可以看到copy修饰的str,在赋值以后,可变字符串strM发生了变化并不会影响str的值。从打印结果来看是因为二者不是一个地址,所以不会相互影响。为什么?是因为copy修饰的属性setter方法,走的是先release旧值,copy新值再赋值给成员变量,不可变copy是深拷贝,就是内容拷贝,地址变化了。不理解的可以看我的另外一篇文章ios属性修饰符的作用。接着看strong修饰的情况。

@property (nonatomic,strong) NSString *str;

直接查看结果

2017-03-14 09:03:36.756 strong修饰不同[968:29890] str----bestDayOfThisYear---0x6000002600c0
2017-03-14 09:03:36.757 strong修饰不同[968:29890] strM----bestDayOfThisYear---0x6000002600c0

结论:被strong修饰以后只是强指针引用,并未改变地址,所以str的值会随着strM进行变化,二者的地址也是相同的。


理解copy和retain的不同

在MRC下进行测试,先看代码

//copy和retain的区别
- (void)viewDidLoad {
    [super viewDidLoad];
    
    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]);
    
}

查看结果

2017-03-14 20:16:59.895 copy和retain的区别[2816:177901] arrMCopy--(
    111,
    222,
    333
)--0x60000005cf80--1
2017-03-14 20:16:59.895 copy和retain的区别[2816:177901] arrMRetain--(
    111,
    222
)--0x60000005cf50--2

结论:copy是深复制,retainCount为1,retain是浅复制,retain是使原来对象引用计数加1,所以arrM和arrMRetain是同一地址,所以remove最后一个元素,arrMRetain也跟着变化了。

六、总结

通过上面的分析,大家可以记住两点:

  1. 原对象和拷贝对象都是不可变对象时,为浅拷贝。
  2. 其他情况均为深拷贝。

具体如下表所示。

总结

致谢

非常感谢 汉斯哈哈哈西木柚子 等技术大牛分享的博客,希望我写的这个文章能帮到过大家,多多交流,有事留言,谢谢大家。我走了,还要继续去搬砖。

相关资料和博文

  1. iOS 浅谈:深.浅拷贝与copy.strong -- 汉斯哈哈哈
  2. 详解iOS的深浅拷贝 -- 西木柚子
  3. IOS深浅拷贝的深入分析 -- omegayy的专栏
  4. apple官方文档
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 157,298评论 4 360
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,701评论 1 290
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 107,078评论 0 237
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,687评论 0 202
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,018评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,410评论 1 211
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,729评论 2 310
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,412评论 0 194
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,124评论 1 239
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,379评论 2 242
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,903评论 1 257
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,268评论 2 251
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,894评论 3 233
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,014评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,770评论 0 192
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,435评论 2 269
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,312评论 2 260

推荐阅读更多精彩内容

  • 1、对象拷贝有两种方式:浅复制和深复制。顾名思义,浅复制,并不拷贝对象本身,仅仅是拷贝指向对象的指针;深复制是直接...
    滴答大阅读 741评论 0 2
  • 简述深浅拷贝 我们实例化的对象存储在堆区,而指向对象的指针一般存储在栈区。我们需要知道这个前提。  实际上拷贝分为...
    朽木自雕也阅读 619评论 1 3
  • 深拷贝和浅拷贝这个问题在面试中常常被问到,而在实际开发中,只要稍有不慎,就会在这里出现问题。尤其对于初学者来说,我...
    西门淋雨阅读 1,734评论 0 1
  • 前言 不敢说覆盖OC中所有copy的知识点,但最起码是目前最全的最新的一篇关于 copy的技术文档了。后续发现有新...
    zyydeveloper阅读 3,244评论 4 35
  • 1.浅拷贝 所谓的浅拷贝,就是指只是将对象内存地址多了一个引用,也就是说,拷贝结束之后,两个对象的值不仅相同,而且...
    小瓶子Zgp阅读 1,574评论 1 2