首先说明两个概念
容器类对象:系统的容器类对象,指NSArray,NSDictionary等
非容器类对象:系统的非容器类对象 这里指的是NSString,NSNumber等一类的对象。
拷贝实际上分为三种:
* 浅拷贝(shallow copy):指针拷贝,对于被拷贝对象的每一层都是指针拷贝,拷贝前后的指针指向同一块内存地址,不产生新的对象,源对象的引用计数器+1。
* 深拷贝(one-level-deep copy):内存块拷贝,拷贝后的指针指向拷贝后的内存块。但是这里有一个坑,就是深拷贝只是深拷贝对象自身这一层,是单层深拷贝,对于容器类对象,容器内各层元素对象依然是浅拷贝。
* 完全深拷贝(real-deep copy): 在完全深拷贝操作时,对于被拷贝对象的每一层都是深拷贝,容器本身的地址和容器内元素的地址都不一样。
不管是容器类对象还是非容器类对象,copy返回的都是不可变对象,mutableCopy返回的是可变对象。 一个NSObject的对象要想使用这两个函数,那么类必须实现NSCopying协议和NSMutableCopying协议,包括系统容器类对象中包含的元素。 对于NSCopying,实现- (id)copyWithZone:(NSZone*)zone方法。 对于NSMutableCopying,实现- (id)mutableCopyWithZone:(NSZone *)zone方法。系统提供的NSString,NSArray,NSDictionary等都已实现,我们自定义的类需要手动实现。
接下来我们看几个例子来看一下copy和mutableCopy的区别:
示例一:非容器类的copy和mutableCopy,以NSString为例
//不可变字符串的浅拷贝和深拷贝
NSString *originStr = [[NSString alloc] initWithFormat:@"这是一个原始字符串"];
NSLog(@"originStr:%p---%p---%@---%ld", &originStr, originStr, originStr,CFGetRetainCount((__bridge CFTypeRef)(originStr)));
NSString*cpStr = [originStrcopy];
NSLog(@"cpStr :%p---%p---%@---%ld---%ld", &cpStr, cpStr, cpStr,CFGetRetainCount((__bridge CFTypeRef)(originStr)),CFGetRetainCount((__bridge CFTypeRef)(cpStr)));
NSString*mcyStr = [originStrmutableCopy];
NSLog(@"mcyStr :%p---%p---%@---%ld---%ld---%ld", &mcyStr, mcyStr, mcyStr,CFGetRetainCount((__bridge CFTypeRef)(originStr)),CFGetRetainCount((__bridge CFTypeRef)(cpStr)),CFGetRetainCount((__bridge CFTypeRef)(mcyStr)));
NSLog(@"—————————————————————————————————");
//不可变字符串的浅拷贝和深拷贝
NSMutableString *moriStr = [[NSMutableString alloc] initWithString:@"这是一个原始字符串"];
NSLog(@"moriStr :%p---%p---%@---%ld", &moriStr, moriStr, moriStr,CFGetRetainCount((__bridge CFTypeRef)(moriStr)));
NSMutableString*mcpStr = [moriStrcopy];
NSLog(@"mcpStr :%p---%p---%@---%ld---%ld", &mcpStr, mcpStr, mcpStr,CFGetRetainCount((__bridge CFTypeRef)(moriStr)),CFGetRetainCount((__bridge CFTypeRef)(mcpStr)));
NSMutableString*mmcyStr = [moriStrmutableCopy];
NSLog(@"mmcyStr :%p---%p---%@---%ld---%ld---%ld", &mmcyStr, mmcyStr, mmcyStr,CFGetRetainCount((__bridge CFTypeRef)(moriStr)),CFGetRetainCount((__bridge CFTypeRef)(mcpStr)),CFGetRetainCount((__bridge CFTypeRef)(mmcyStr)));
运行看一下输出:从左往右依次输出拷贝操作后指针地址,对象内存地址,对象,引用计数
originStr:0x7ffeefbe41b8---0x60000334e790---这是一个原始字符串---1
cpStr :0x7ffeefbe41b0---0x60000334e790---这是一个原始字符串---2---2
mcyStr :0x7ffeefbe41a8---0x60000337a4c0---这是一个原始字符串---2---2---1
2020-07-08 13:43:48.023203+0800 iOS_copy[4153:3626248] —————————
moriStr :0x7ffeefbe41a0---0x60000337c9f0---这是一个原始字符串---1
mcpStr :0x7ffeefbe4198---0x60000337cae0---这是一个原始字符串---1---1
mmcyStr :0x7ffeefbe4190---0x60000337cc00---这是一个原始字符串---1---1---1
先看分割线上面不可变字符串的copy和mutableCopy。copy操作生成新的指针指向当前对象地址,引用计数在copy操作后变成了2,说明copy操作对不可变对象会引起源对象引用计数变化。mutableCopy指针地址和对象内存地址都是新生成的,mutableCopy后的对象是引用计数为1的新对象,源对象引用计数不受影响。
再看分割线下面可变字符串的copy和mutableCopy。可见无论是copy还是mutableCopy操作,都是生成新的指针指向新开辟的内存,源对象和新对象引用计数都为 1 ,引用计数不受影响。
示例二:元素为不可变的不可变容器的copy和mutableCopy
NSArray*originArray =@[@"北京市",@[@"昌平区",@[@"天通苑"]]];
NSLog(@"originArray:%@---%p---%p---%p---%ld", [originArrayclass], &originArray, originArray, originArray[0],CFGetRetainCount((__bridge CFTypeRef)(originArray)));
NSArray*cyArray = [originArraycopy];
NSLog(@"cyArray :%@---%p---%p---%p---%ld---%ld", [cyArrayclass], &cyArray, cyArray, cyArray[0],CFGetRetainCount((__bridge CFTypeRef)(originArray)),CFGetRetainCount((__bridge CFTypeRef)(cyArray)));
NSArray*mcyArray = [originArraymutableCopy];
NSLog(@"mcyArray :%@---%p---%p---%p---%ld---%ld", [mcyArrayclass], &mcyArray, mcyArray, mcyArray[0],CFGetRetainCount((__bridge CFTypeRef)(originArray)),CFGetRetainCount((__bridge CFTypeRef)(mcyArray)));
运行结果:数组的类型---指向对象的指针的地址---对象内存地址---首元素内存地址----引用计数
originArray:__NSArrayI---0x7ffeee273188---0x600000cf89a0---0x10198d060---1 cyArray :__NSArrayI---0x7ffeee273180---0x600000cf89a0---0x10198d060---2---2 mcyArray :__NSArrayM---0x7ffeee273178---0x6000002c33c0---0x10198d060---2---1
__NSArrayI <--> NSArray __NSArrayM <--> NSMutableArray
可以看到对originArray进行copy操作后的cyArray是NSArray不可变类型,生成了新的指针,指向了和originArray同一块内存地址,数组内元素地址没有变化。同时cyArray和originArray的引用计数为 2,说明对不可变对象的copy操作会引起对象引用计数加 1 ,与上面例子中相印证。
对originArray进行了mutableCopy操作后的mcyArray是NSMutableArray可变数组,生成了新的指针,同时开辟新的内存地址,但是数组内的元素地址没有变化。源对象originArray引用计数仍然为 2,mcyArray是引用计数为 1 的新对象。
示例二:元素可变的不可变容器的copy和mutableCopy
NSMutableArray *towns = [[NSMutableArray alloc] initWithObjects:@"TianTongyuan", @"Huilongguan", nil];
NSMutableArray *disticts = [[NSMutableArray alloc] initWithObjects:@"Changping", towns, nil];
NSArray *originArray = @[@"Beijing", disticts];
NSLog(@"originArray:%@---%p---%p---%p---%ld", [originArray class], &originArray, originArray, originArray[0], CFGetRetainCount((__bridge CFTypeRef)(originArray)));
NSArray *cyArray = [originArray copy];
NSLog(@"cyArray :%@---%p---%p---%p---%ld---%ld", [cyArray class], &cyArray, cyArray, cyArray[0], CFGetRetainCount((__bridge CFTypeRef)(originArray)), CFGetRetainCount((__bridge CFTypeRef)(cyArray)));
NSArray *mcyArray = [originArray mutableCopy];
NSLog(@"mcyArray :%@---%p---%p---%p---%ld---%ld", [mcyArray class], &mcyArray, mcyArray, mcyArray[0], CFGetRetainCount((__bridge CFTypeRef)(originArray)), CFGetRetainCount((__bridge CFTypeRef)(mcyArray)));
NSLog(@"%@---\n%@---\n%@", originArray[1], cyArray[1], mcyArray[1]);
[disticts addObject:@"Haidian"];
NSLog(@"%@---\n%@---\n%@", originArray[1], cyArray[1], mcyArray[1]);
运行结果
originArray:__NSArrayI---0x7ffedfe5c190---0x60000355f480---0x10fda40c0---1
cyArray :__NSArrayI---0x7ffedfe5c188---0x60000355f480---0x10fda40c0---2---2
mcyArray :__NSArrayM---0x7ffedfe5c180---0x600003b14b10---0x10fda40c0---2---1
2020-07-08 14:52:25.644210+0800 iOS_copy[6633:3675474] (
Changping,
(
TianTongyuan,
Huilongguan
)
)---originArray[1]
(
Changping,
(
TianTongyuan,
Huilongguan
)
)---cyArray[1]
(
Changping,
(
TianTongyuan,
Huilongguan
)
)---mcyArray[1]
2020-07-08 14:52:25.644368+0800 iOS_copy[6633:3675474] (
Changping,
(
TianTongyuan,
Huilongguan
),
Haidian
)---originArray[1]
(
Changping,
(
TianTongyuan,
Huilongguan
),
Haidian
)---cyArray[1]
(
Changping,
(
TianTongyuan,
Huilongguan
),
Haidian
)---mcyArray[1]
对于copy和mutableCopy前后原对象和新对象指针和内存地址以及引用计数参考上面示例二。
与示例二区别的是后面 disticts 数组改变前后的输出,在向disticts内添加@“Haidian”元素后,三个数组中对应disticts的元素originArray[1],cyArray[1],mcyArray[1],都相应变化。因为无论是copy还是mutableCopy,都没有对数组内元素深拷贝,只是生成新的元素指针指向同一个对应的内存地址,当这个元素是可变数组且发生变化是,拷贝操作前后的数组内对应元素都会相应变化。
示例三 包含可变元素的可变容器的拷贝
NSMutableArray *towns = [[NSMutableArray alloc] initWithObjects:@"TianTongyuan", @"Huilongguan", nil];
NSMutableArray *disticts = [[NSMutableArray alloc] initWithObjects:@"Changping", towns, nil];
NSMutableArray *originArray = [[NSMutableArray alloc] initWithObjects:@"Beijing", disticts, nil];
NSLog(@"originArray:%@---%p---%p---%p---%ld", [originArrayclass], &originArray, originArray, originArray[1],CFGetRetainCount((__bridge CFTypeRef)(originArray)));
NSArray*cyArray = [originArraycopy];
NSLog(@"cyArray :%@---%p---%p---%p---%ld---%ld", [cyArrayclass], &cyArray, cyArray, cyArray[1],CFGetRetainCount((__bridge CFTypeRef)(originArray)),CFGetRetainCount((__bridge CFTypeRef)(cyArray)));
NSArray*mcyArray = [originArraymutableCopy];
NSLog(@"mcyArray :%@---%p---%p---%p---%ld---%ld", [mcyArrayclass], &mcyArray, mcyArray, mcyArray[1],CFGetRetainCount((__bridge CFTypeRef)(originArray)),CFGetRetainCount((__bridge CFTypeRef)(mcyArray)));
NSLog(@"%@---originArray\n%@---cyArray\n%@---mcyArray", originArray, cyArray, mcyArray);
[originArrayaddObject:@"Tianjin"];
[distictsaddObject:@"Haidian"];
NSLog(@"%@---originArray\n%@---cyArray\n%@---mcyArray", originArray, cyArray, mcyArray);
运行结果
originArray:__NSArrayM---0x7ffee84181a8---0x60000245e730---0x60000245ea00---1 cyArray :__NSArrayI---0x7ffee84181a0---0x600002a2c540---0x60000245ea00---1---1
mcyArray :__NSArrayM---0x7ffee8418198---0x60000243dc50---0x60000245ea00---1---1
2020-07-08 15:15:30.426782+0800 iOS_copy[7384:3690010] (
Beijing,
(
Changping,
(
TianTongyuan,
Huilongguan
)
)
)---originArray
(
Beijing,
(
Changping,
(
TianTongyuan,
Huilongguan
)
)
)---cyArray
(
Beijing,
(
Changping,
(
TianTongyuan,
Huilongguan
)
)
)---mcyArray
2020-07-08 15:15:30.427023+0800 iOS_copy[7384:3690010] (
Beijing,
(
Changping,
(
TianTongyuan,
Huilongguan
),
Haidian
),
Tianjin
)---originArray
(
Beijing,
(
Changping,
(
TianTongyuan,
Huilongguan
),
Haidian
)
)---cyArray
(
Beijing,
(
Changping,
(
TianTongyuan,
Huilongguan
),
Haidian
)
)---mcyArray
执行了copy后得到的cyArray 是不可变数组, mutableCopy后得到的 mcyArray是可变,两个数组都是生成新的指针指向新开辟的内存块,因此,源数组内直接添加元素@"Tianjin"并不会引起拷贝后数组的变化。我们还看到拷贝前后数组内元素地址没变化,因为数组内元素不变,所以我们再向disticts内添加@“Haidian”后,三个数组对应元素的值都发生了变化。
小结
1、对于系统的非容器类对象(NSString,NSNumber等),如果对一不可变对象进行拷贝,copy是指针拷贝(浅拷贝),而mutableCopy就是内存块拷贝(深拷贝)。如果是对一可变对象拷贝,都是深拷贝,但是copy返回的对象是不可变的。
2、对于系统的容器类对象(NSArray,NSDictionary等),如果对一不可变对象进行拷贝,包括对象指针和对象内元素指针,copy是指针拷贝(浅拷贝),而mutableCopy只是对对象本身是内存块拷贝(深拷贝),对象内元素依然是指针拷贝(浅拷贝)。如果是对一可变对象拷贝,都是对对象本身是内存块拷贝(深拷贝),对象内元素依然是指针拷贝(浅拷贝),但是copy返回的对象是不可变的。
3、对于容器类对象中包含的可变元素,当这个元素改变(增,删,改)时,copy和mutableCopy后的对象中对应元素也会跟随变化,这个需要注意。
示例四 多层深拷贝
上面在对容器类进行深拷贝是只是深拷贝了容器这一层,对于容器内元素对象依然是浅拷贝,怎样才能对元素对象也进行深拷贝呢?NSArray有一个- (instancetype)initWithArray:(NSArray *)array copyItems:(BOOL)flag;函数,我们可以先用一下。
当flag为 NO 时
NSMutableArray *estates = [[NSMutableArray alloc] initWithObjects:@"Longtengyuan", nil];
NSMutableArray *towns = [[NSMutableArray alloc] initWithObjects:@"Huilongguan", estates, nil];
NSMutableArray *disticts = [[NSMutableArray alloc] initWithObjects:@"Changping", towns, nil];
NSMutableArray *originArray = [[NSMutableArray alloc] initWithObjects:@"Beijing", disticts, nil];
NSMutableArray *array1 = [[NSMutableArray alloc] initWithArray:originArray copyItems:NO];
NSArray *array2 = [[NSArray alloc] initWithArray:originArray copyItems:NO];
NSLog(@"%@-%p-%p----%@-%p-%p", [array1class], array1, array1[1], [array2class], array2, array2[1]);
NSArray*originArray1 = [[NSArrayalloc]initWithObjects:@"Beijing", disticts,nil];
NSMutableArray *array3 = [[NSMutableArray alloc] initWithArray:originArray1 copyItems:NO];
NSArray *array4 = [[NSArray alloc] initWithArray:originArray1 copyItems:NO];
NSLog(@"%@-%p-%p----%@-%p-%p", [array3class], array3, array3[1], [array4class], array4, array4[1]);
运行结果
__NSArrayM-0x600001115050-0x600001114ff0----__NSArrayI-0x600001f64940-0x600001114ff0
__NSArrayM-0x60000112def0-0x600001114ff0----__NSArrayI-0x600001f722a0-0x600001114ff0
可以看出flag为 NO 时,这个函数都是对源对象进行了深拷贝,但是仅限于数组自身单层深拷贝,数组内各层元素对象依然是浅拷贝,返回是否可变对象取决于是否使用可变类创建。
flag为YES时:
NSMutableArray *estates = [[NSMutableArray alloc] initWithObjects:@"Longtengyuan", nil];
NSMutableArray *towns = [[NSMutableArray alloc] initWithObjects:@"Huilongguan", estates, nil];
NSMutableArray *disticts = [[NSMutableArray alloc] initWithObjects:@"Changping", towns, nil];
NSMutableArray *originArray = [[NSMutableArray alloc] initWithObjects:@"Beijing", disticts, nil];
NSMutableArray *array1 = [[NSMutableArray alloc] initWithArray:originArray copyItems:YES];
NSArray *array2 = [[NSArray alloc] initWithArray:originArray copyItems:YES];
NSLog(@"%p--%p--%p--%@", array1, array1[1], array1[1][1], [array1[1][1]class]);
NSLog(@"%p--%p--%p--%@",array2, array2[1], array2[1][1], [array2[1][1]class]);
[estatesaddObject:@"Longhuayuan"];
NSLog(@"%@", array1[1][1]);
NSLog(@"%@", array2[1][1]);
运行结果
0x600003086a60--0x600003ea2000--0x600003086730--__NSArrayM
0x600003ea1f80--0x600003ea1f60--0x600003086730--__NSArrayM
2020-07-08 16:49:30.747854+0800 iOS_copy[10520:3756490] (
Huilongguan,
(
Longtengyuan,
Longhuayuan
)
)
2020-07-08 16:49:30.748034+0800 iOS_copy[10520:3756490] (
Huilongguan,
(
Longtengyuan,
Longhuayuan
)
)
flag为YES,我们看到数组内第一层元素地址也发生了变化,但是第二层的元素地址没变化。
这说明- (instancetype)initWithArray:(NSArray *)array copyItems:(BOOL)flag函数在flag为YES时也只是对源对象本身和第一层的元素进行了深拷贝,更深的元素依然是浅拷贝。这依然不是我们想要的的完全深拷贝。 虽然写了多层深拷贝,实质也就是双重深拷贝。
示例五 真正的完全深拷贝
NSMutableArray *estates = [[NSMutableArray alloc] initWithObjects:@"Longtengyuan", @"Longboyuan", nil];
NSMutableArray *towns = [[NSMutableArray alloc] initWithObjects:@"Huilongguan", estates, nil];
NSMutableArray *disticts = [[NSMutableArray alloc] initWithObjects:@"Changping", towns, nil];
NSMutableArray *originArray = [[NSMutableArray alloc] initWithObjects:@"Beijing", disticts, nil];
// if (@available(iOS 12.0, *)) {
// NSArray *deepCpArray = [NSKeyedUnarchiver unarchivedObjectOfClass:[originArray class] fromData:[NSKeyedArchiver archivedDataWithRootObject:originArray requiringSecureCoding:NO error:nil] error:nil];
// }
NSArray *deepCpArray = [NSKeyedUnarchiver unarchiveObjectWithData:[NSKeyedArchiver archivedDataWithRootObject:originArray]];
NSLog(@"%p--%p--%p--%@", originArray, originArray[1], originArray[1][1], [originArray[1][1]class]);
NSLog(@"%p--%p--%p--%@", deepCpArray, deepCpArray[1], deepCpArray[1][1], [deepCpArray[1][1]class]);
[estatesaddObject:@"Longhuayuan"];
NSLog(@"%@", originArray[1][1]);
NSLog(@"%@", deepCpArray[1][1]);
运行Log:
0x600003f902d0--0x600003f90330--0x600003f90210--__NSArrayM
0x600003f90510--0x600003f90150--0x600003f901e0--__NSArrayM
2020-07-08 17:23:54.148555+0800 iOS_copy[11623:3775644] (
Huilongguan,
(
Longtengyuan,
Longboyuan,
Longhuayuan
)
)
2020-07-08 17:23:54.148683+0800 iOS_copy[11623:3775644] (
Huilongguan,
(
Longtengyuan,
Longboyuan
)
)
如偿所愿,使用归档解档的方法,实现了数组和数组内的每层元素的深拷贝。需要注意的是,数组内的元素需要实现NSCoding协议。