君不见,高堂明镜悲白发,朝如青丝暮成雪
前记
最近在项目中,用到了关于copy
的问题,当我对自己定义的模型进行拷贝的时候,居然没有实现想要的深拷贝,于是出了点小问题,让我有点小郁闷,所以决定好好深入研究下copy
与mutableCopy
与NSDictionary
、NSArray
、model
之间的小事~
copy 、mutableCopy
先来看看这两个的区别吧
在分析区别之前,我们先上一段简单的代码
//非集合类测试copy 和 mutablecopy
NSString *string = @"abc";
NSString *copyString = [string copy];
//mutableCopy 后位动态string
NSString *mutableCopyString = [string mutableCopy];
NSString *mutableCopy_copy_String = [mutableCopyString copy];
NSMutableString *mutableCopy_mutable_copy_String = [mutableCopyString mutableCopy];
NSLog(@" 打印信息:%p---%p---%p---%p---%p",string,copyString,mutableCopyString,mutableCopy_copy_String,mutableCopy_mutable_copy_String);
//集合类型测试copy 和 mutablecopy
NSDictionary *_dic = @{@"key1":@"abc",@"key2":@"cde"};
NSDictionary *copyDic = [_dic copy];
NSMutableDictionary *mutableCopyDic = [_dic mutableCopy];
[mutableCopyDic setValue:@"efg" forKey:@"key3"];
NSMutableDictionary *mutableCopy_copy_Dic = [mutableCopyDic copy];
NSMutableDictionary *mutableCopy_mutableCopy_Dic = [mutableCopyDic mutableCopy];
NSLog(@" 打印信息字典拷贝:%p---%p---%p---%p---%p",_dic,copyDic,mutableCopyDic,mutableCopy_copy_Dic,mutableCopy_mutableCopy_Dic);
//打印结果
2017-08-17 15:06:33.277 GLDeepCopy[27242:8168435] 打印信息:0x10b30c0e8---0x10b30c0e8---0x608000074e00---0xa000000006362613---0x608000074e40
2017-08-17 15:06:33.278 GLDeepCopy[27242:8168435] 打印信息字典拷贝:0x600000072800---0x600000072800---0x60000005c8c0---0x600000072840---0x60000005cb30
顺便插个图片
-
copy
:对我们的对象进行拷贝,返回一个非动态的对象 -
mutableCopy
:对我们的对象进行拷贝,返回一个动态的对象
通过上图,我们可以看到,虽然我们用的是NSString
来接收[string mutableCopy]
,但是实际上得到的却是一个NSMutableString
类型的对象,这里不得不说一个比较严重的问题,就是如果我们在定义一个动态类型为@property (nonatomic,copy)
属性后,一定不能执行copy
操作,否则在你执行修改动态类型内容的时候,一定会crash
,因为当你copy
后,你的动态类型已经变成了一个不可变类型
下面我们来看上面贴的代码,从代码分析,我们可以看到,非集合类型和集合类型中的非动态类型,在copy
后,地址还是一样的,没有变化,而除此之外,地址都发生了变化,我们可以得到下面的结论
- 非集合类型和集合类型非动态类型在
copy
后,执行的是浅拷贝
(即对指针进行拷贝) - 非集合类型和集合类型非动态类型在
mutableCopy
后,执行的是深拷贝
(即对对象进行拷贝),且返回动态类型 - 非集合类型和集合类型动态类型在
copy
后,执行的是深拷贝
- 非集合类型和集合类型动态类型在
mutableCopy
后,执行的是深拷贝
,且返回动态类型
上面的结论也不是那么准确,怎么说呢?容我慢慢分析
对于此处的深拷贝,我想大家一定想的是完完全全的拷贝吧,是的,我一开始也是这么想的,但是有没有想过测试下,当我们的集合有很多层的情况呢?下面就举个例子来说明吧
代码走起
@interface PersonModel : NSObject<NSCopying>
@property (nonatomic,copy) NSString *name;
@property (nonatomic,assign) NSUInteger age;
@end
@implementation PersonModel
- (id)copyWithZone:(NSZone *)zone{
PersonModel *copy = [[PersonModel alloc] init];
if (copy) {
copy.name = [self.name copyWithZone:zone];
copy.age = self.age;
copy.array = [self.array copyWithZone:zone];
}
return copy;
}
@end
{
//测试加入数组中 对数组进行拷贝
PersonModel *new_PersonModel = [[PersonModel alloc] init];
new_PersonModel.name = @"gaogaogao";
new_PersonModel.age = 20;
NSLog(@" 打印信息修改后 personModel :%@\n new_PersonModel:%@",personModel,new_PersonModel);
NSArray *array = [NSArray arrayWithObject:new_PersonModel];
NSArray *copyArray = [array copy];
NSMutableArray *mutableCopyArray = [array mutableCopy];
NSLog(@" 打印信息array:%p+%@\ncopyArray:%p+%@\nmutableCopyArray:%p+%@",array,array,copyArray,copyArray,mutableCopyArray,mutableCopyArray);
//修改数组中的内容
PersonModel *personModel_array = array[0];
personModel_array.name = @"gao_array";
personModel_array.age = 24;
NSLog(@" 打印信息修改后array:%p+%@\ncopyArray:%p+%@\nmutableCopyArray:%p+%@",array,array,copyArray,copyArray,mutableCopyArray,mutableCopyArray);
}
//打印结果
2017-08-17 15:31:31.136 GLDeepCopy[27447:8197824] 打印信息array:0x608000017590+(
"{\n age = 20;\n name = gaogaogao;\n}"
)
copyArray:0x608000017590+(
"{\n age = 20;\n name = gaogaogao;\n}"
)
mutableCopyArray:0x608000243570+(
"{\n age = 20;\n name = gaogaogao;\n}"
)
2017-08-17 15:31:31.164 GLDeepCopy[27447:8197824] 打印信息修改后array:0x608000017590+(
"{\n age = 24;\n name = \"gao_array\";\n}"
)
copyArray:0x608000017590+(
"{\n age = 24;\n name = \"gao_array\";\n}"
)
mutableCopyArray:0x608000243570+(
"{\n age = 24;\n name = \"gao_array\";\n}"
)
从上面的打印信息中,我们看mutableCopyArray
的地址,可以发现,地址是发生了改变的,说明我们现在是执行了深拷贝的,但是看打印信息修改后array
这个位置,你会发现,后面的内容在修改后,居然变成一样的了,但是我们明明修改的是array[0]
中的对象的值啊,什么鬼👻,是不是有点郁闷,那么下面我修改下打印信息,再来看看
NSLog(@" 打印信息array:%p+%@ + %p\ncopyArray:%p+%@ + %p\nmutableCopyArray:%p+%@ + %p",array,array,array[0],copyArray,copyArray,copyArray[0],mutableCopyArray,mutableCopyArray,mutableCopyArray[0]);
//打印信息
2017-08-17 15:43:51.018 GLDeepCopy[27521:8211644] 打印信息array:0x6080000089f0+(
"{\n age = 20;\n name = gaogaogao;\n}"
) + 0x6000000352e0
copyArray:0x6080000089f0+(
"{\n age = 20;\n name = gaogaogao;\n}"
) + 0x6000000352e0
mutableCopyArray:0x60800005bdb0+(
"{\n age = 20;\n name = gaogaogao;\n}"
) + 0x6000000352e0
是的,可以看到,三个数组里面的对象是同一个对象,也就是,数组虽然进行了深拷贝,但是并没有对里面的内容进行深拷贝,而是执行的指针拷贝,瞬间有种懵逼的感觉,特么搞了这么久,还是不能执行完全的深拷贝。所以,上面我们总结的4
点中关于集合类型
的深拷贝
不能算是真正意义上的深拷贝
,只能说是一种单层的深拷贝
,即至少有一层是执行了深拷贝
的,但是在实际中,我们往往需要的是完完全全的完全深拷贝
,那么针对集合类型,我们又该怎么来实现完全深拷贝
呢?
集合的完全深拷贝
关于集合的完全深拷贝,这里有两种方法
- 通过归档的方式来实现(后面会讲到)
- 通过使用集合自带的方法来实现(并不完美)
先来讲讲 这个并不完美的方法吧
以上面的例子来将,我们可以使用NSArray
中的
- (instancetype)initWithArray:(NSArray<ObjectType> *)array copyItems:(BOOL)flag;
当flag
参数设置为YES
的时候,集合里的每个对象都会去执行copyWithZone
方法,当然这就必须要求我们的对象必须遵循 NSCopying
协议,并且实现copyWithZone
方法,此时对象就会被深复制到新的集合。如果对象没有遵循 NSCopying
协议,而用这种方法进行深拷贝,就会在运行时出错
来实现,当然NSDictionary
和NSSet
也有对应的方法
- (instancetype)initWithDictionary:(NSDictionary<KeyType, ObjectType> *)otherDictionary copyItems:(BOOL)flag;
- (instancetype)initWithSet:(NSSet<ObjectType> *)set copyItems:(BOOL)flag;
先看修改后的
{
//测试加入数组中 对数组进行拷贝
PersonModel *new_PersonModel = [[PersonModel alloc] init];
new_PersonModel.name = @"gaogaogao";
new_PersonModel.age = 20;
NSLog(@" 打印信息修改后 personModel :%@\n new_PersonModel:%@",personModel,new_PersonModel);
NSArray *array = [NSArray arrayWithObject:new_PersonModel];
NSArray *copyArray = [array copy];
NSMutableArray *mutableCopyArray = [[NSMutableArray alloc] initWithArray:array copyItems:YES];//[array mutableCopy];
NSLog(@" 打印信息array:%p+%@ + %p\ncopyArray:%p+%@ + %p\nmutableCopyArray:%p+%@ + %p",array,array,array[0],copyArray,copyArray,copyArray[0],mutableCopyArray,mutableCopyArray,mutableCopyArray[0]);
//修改数组中的内容
PersonModel *personModel_array = array[0];
personModel_array.name = @"gao_array";
personModel_array.age = 24;
NSLog(@" 打印信息修改后array:%p+%@\ncopyArray:%p+%@\nmutableCopyArray:%p+%@",array,array,copyArray,copyArray,mutableCopyArray,mutableCopyArray);
}
//打印结果
2017-08-17 16:03:48.954 GLDeepCopy[27694:8234722] 打印信息array:0x6080000030f0+(
"{\n age = 20;\n name = gaogaogao;\n}"
) + 0x6080000284c0
copyArray:0x6080000030f0+(
"{\n age = 20;\n name = gaogaogao;\n}"
) + 0x6080000284c0
mutableCopyArray:0x60800004cae0+(
"{\n age = 20;\n name = gaogaogao;\n}"
) + 0x608000028420
2017-08-17 16:03:48.955 GLDeepCopy[27694:8234722] 打印信息修改后array:0x6080000030f0+(
"{\n age = 24;\n name = \"gao_array\";\n}"
)
copyArray:0x6080000030f0+(
"{\n age = 24;\n name = \"gao_array\";\n}"
)
mutableCopyArray:0x60800004cae0+(
"{\n age = 20;\n name = gaogaogao;\n}"
)
从上面的结果,我们可以看出,最终实现了我们想要的效果,对数组实现了完全深拷贝
,从这里看上去,还是挺完美的嘛,然而在实际中,你知道的,肯定不止这么多层,下面我们再改造下,在PersonModel
中增加一个array
,并且新增一个类Anima
@interface Anima : NSObject<NSCopying>
@property (nonatomic,copy) NSString *address;
@end
@implementation Anima
- (id)copyWithZone:(NSZone *)zone{
Anima *copy = [[Anima alloc] init];
if (copy) {
copy.address = [self.address copyWithZone:zone];
}
return copy;
}
@end
@interface PersonModel : NSObject<NSCopying,NSMutableCopying>
@property (nonatomic,copy) NSString *name;
@property (nonatomic,assign) NSUInteger age;
@property (nonatomic,strong) NSMutableArray *array;
@end
.
.
.
//实现部分
//测试加入数组中 对数组进行拷贝
PersonModel *new_PersonModel = [[PersonModel alloc] init];
new_PersonModel.name = @"gaogaogao";
new_PersonModel.age = 20;
Anima *anima = [[Anima alloc] init];
anima.address = @"成都";
new_PersonModel.array = [NSMutableArray arrayWithObject:anima];
NSArray *array = [NSArray arrayWithObject:new_PersonModel];
NSArray *copyArray = [array copy];
NSMutableArray *mutableCopyArray = [[NSMutableArray alloc] initWithArray:array copyItems:YES];//[array mutableCopy];//
NSLog(@" 打印信息array:%p+%@ + %p\ncopyArray:%p+%@ + %p\nmutableCopyArray:%p+%@ + %p",array,array,array[0],copyArray,copyArray,copyArray[0],mutableCopyArray,mutableCopyArray,mutableCopyArray[0]);
//修改数组中的内容
PersonModel *personModel_array = array[0];
personModel_array.name = @"gao_修改";
personModel_array.age = 24;
Anima *anima_model = personModel_array.array[0];
anima_model.address = @"阆中";
//获取深拷贝数组中的model中数组中的model
PersonModel *personModel_mutable_copy_array = mutableCopyArray[0];
Anima *anima_model_mutable_copy = personModel_mutable_copy_array.array[0];
NSLog(@" 打印信息获取深拷贝数组中的model中数组中的model地址:%p ++ %p",anima_model,anima_model_mutable_copy);
NSLog(@" 打印信息修改后array:%p+%@\ncopyArray:%p+%@\nmutableCopyArray:%p+%@",array,array,copyArray,copyArray,mutableCopyArray,mutableCopyArray);
//打印信息 截取部分说明
2017-08-18 15:27:32.324 GLDeepCopy[69373:9095757] 打印信息获取深拷贝数组中的model中数组中的model地址:0x608000013100 ++ 0x608000013100
从上面的结果分析,当层次一多,该方法也并不能完成我们想要的完全拷贝
,那么我们除了用归档
的方式,难道就没有其他方式了么?答案肯定是 还有的,可以这么设想一下,集合无外乎就是装了很多东西,既然是这样,我们何必不针对集合里面的内容层层进行深拷贝呢?是的,我们可以通过遍历的方式来进行,为了方便使用,可以新建一个Category
,于是乎就有了下面的代码,我们先以NSArray
为例
#import <Foundation/Foundation.h>
@interface NSArray (GLDeepCopy)
/**
返回当前类型
@return 返回
*/
- (instancetype)GLDeepCopy;
@end
#import "NSArray+GLDeepCopy.h"
@implementation NSArray (GLDeepCopy)
- (instancetype)GLDeepCopy
{
NSMutableArray *resultMutableArray = [[NSMutableArray alloc] initWithCapacity:self.count];
for (id object in self) {
//定义一个id类型来接收拷贝后的
id copyObject = nil;
//如果该对象有该方法
if ([object respondsToSelector:@selector(GLDeepCopy)]) {
copyObject = [object GLDeepCopy];
//判断该对象是否实现了NSCopying 协议的方法 如果是 则进行copy
}else if ([object conformsToProtocol:@protocol(NSCopying)]){
copyObject = [object copy];
}else if ([object conformsToProtocol:@protocol(NSMutableCopying)]){
copyObject = [object mutableCopy];
}else{
copyObject = object;
}
[resultMutableArray addObject:copyObject];
}
if ([self isKindOfClass:[NSArray class]]) {
return [NSArray arrayWithArray:resultMutableArray];
}else{
return resultMutableArray;
}
}
@end
在上面方法中,我们通过遍历数组中的内容来进行循环拷贝,[object respondsToSelector:@selector(GLDeepCopy)]
,这一句是如果我们的对象实现了该方法,我们就去调用,比如当前对象是数组,字典,NSSet
或者是自定义数据模型,针对自定义数据模型,如果包含集合类型,而且又需要进行完全拷贝
的时候,最好实现GLDeepCopy
方法,后面有例子。如果没有集合类型,但是需要完全拷贝
的话,就可以通过实现NSCopying
或者NSMutableCopying
协议来实现。
#import <Foundation/Foundation.h>
@interface PersonModel : NSObject<NSCopying,NSMutableCopying>
@property (nonatomic,copy) NSString *name;
@property (nonatomic,assign) NSUInteger age;
@property (nonatomic,strong) NSMutableArray *array;
@end
#import "PersonModel.h"
#import "NSArray+GLDeepCopy.h"
#import <objc/runtime.h>
@interface PersonModel()
@end
@implementation PersonModel
/**
描述信息
@return 返回描述信息 利于我们在debug 的时候方便查看
*/
- (NSString *)description
{
return [NSString stringWithFormat:@"%@",[self getObjectData:self]];
}
#pragma mark == Copying协议
- (id)copyWithZone:(NSZone *)zone{
PersonModel *copy = [[PersonModel alloc] init];
if (copy) {
copy.name = [self.name copyWithZone:zone];
copy.age = self.age;
copy.array = [self.array copyWithZone:zone];
}
return copy;
}
- (id)mutableCopyWithZone:(NSZone *)zone
{
PersonModel *copy = [[PersonModel alloc] init];
if (copy) {
copy.name = [self.name mutableCopyWithZone:zone];
copy.age = self.age;
copy.array = [self.array mutableCopyWithZone:zone];
}
return copy;
}
#pragma mark == 深拷贝
//(如果没有集合类型,可以不用执行)
- (id)GLDeepCopy
{
PersonModel *personModel = [[PersonModel alloc] init];
personModel.age = self.age;
personModel.name = [self.name copy];
personModel.array = [self.array GLDeepCopy];
return personModel;
}
#pragma mark == 转换
/**
将对象转为NSDictionary
@param obj 对象
@return 返回的NSDictionary
*/
- (NSDictionary*)getObjectData:(id)obj
{
NSMutableDictionary *dic = [NSMutableDictionary dictionary];
unsigned int propsCount;
objc_property_t *props = class_copyPropertyList([obj class], &propsCount);
for(int i = 0;i < propsCount; i++)
{
objc_property_t prop = props[i];
NSString *propName = [NSString stringWithUTF8String:property_getName(prop)];
id value = [obj valueForKey:propName];
if(value == nil)
{
value = [NSNull null];
}
else
{
value = [self getObjectInternal:value];
}
[dic setObject:value forKey:propName];
}
return dic;
}
/**
针对对象里面属性的不同属性 进行转换
@param obj 对象
@return 返回
*/
- (id)getObjectInternal:(id)obj
{
if([obj isKindOfClass:[NSString class]]
|| [obj isKindOfClass:[NSNumber class]]
|| [obj isKindOfClass:[NSNull class]])
{
return obj;
}
if([obj isKindOfClass:[NSArray class]])
{
NSArray *objarr = obj;
NSMutableArray *arr = [NSMutableArray arrayWithCapacity:objarr.count];
for(int i = 0;i < objarr.count; i++)
{
[arr setObject:[self getObjectInternal:[objarr objectAtIndex:i]] atIndexedSubscript:i];
}
return arr;
}
if([obj isKindOfClass:[NSDictionary class]])
{
NSDictionary *objdic = obj;
NSMutableDictionary *dic = [NSMutableDictionary dictionaryWithCapacity:[objdic count]];
for(NSString *key in objdic.allKeys)
{
[dic setObject:[self getObjectInternal:[objdic objectForKey:key]] forKey:key];
}
return dic;
}
return [self getObjectData:obj];
}
在上面的自定义模型中,我们包含NSMutableArray
,而且我们希望实现完全拷贝
,所以实现了GLDeepCopy
,并且针对其中的NSMutableArray
调用了Category
中的 GLDeepCopy
方法,在看另一个模型
#import <Foundation/Foundation.h>
@interface Anima : NSObject<NSCopying>
@property (nonatomic,copy) NSString *address;
@end
#import "Anima.h"
@interface Anima ()
@end
@implementation Anima
- (id)copyWithZone:(NSZone *)zone{
Anima *copy = [[Anima alloc] init];
if (copy) {
copy.address = [self.address copyWithZone:zone];
}
return copy;
}
@end
由于该模型,没有包含集合类型,所以我只是简单的实现了NSCopying
协议,下面来看使用情况
//测试加入数组中 对数组进行拷贝
PersonModel *new_PersonModel = [[PersonModel alloc] init];
new_PersonModel.name = @"gaogaogao";
new_PersonModel.age = 20;
Anima *anima = [[Anima alloc] init];
anima.address = @"成都";
new_PersonModel.array = [NSMutableArray arrayWithObject:anima];
[new_PersonModel.array addObject:@[@"1",@"2",@"3"]];
NSArray *array = [NSArray arrayWithObject:new_PersonModel];
NSArray *copyArray = [array copy];
NSMutableArray *mutableCopyArray = [NSMutableArray arrayWithArray:[array GLDeepCopy]];//[[NSMutableArray alloc] initWithArray:array copyItems:YES];//[array mutableCopy];//
NSLog(@" 打印信息array:%p+%@ + %p\ncopyArray:%p+%@ + %p\nmutableCopyArray:%p+%@ + %p",array,array,array[0],copyArray,copyArray,copyArray[0],mutableCopyArray,mutableCopyArray,mutableCopyArray[0]);
//修改数组中的内容
PersonModel *personModel_array = array[0];
personModel_array.name = @"gao_修改";
personModel_array.age = 24;
Anima *anima_model = personModel_array.array[0];
anima_model.address = @"阆中";
//获取深拷贝数组中的model中数组中的model
PersonModel *personModel_mutable_copy_array = mutableCopyArray[0];
Anima *anima_model_mutable_copy = personModel_mutable_copy_array.array[0];
NSLog(@" 打印信息获取深拷贝数组中的model中数组中的model地址:\n原数组中的PersonModel中Array中的Anima:%p \n 原数组中的PersonModel中Array中的非Anima对象%p\n++ 深拷贝数组中的PersonModel中Array中的Anima:%p \n 深拷贝数组中的PersonModel:%p \n+ 深拷贝数组中的PersonModel中Array中的非Anima对象%p",anima_model,personModel_array.array[1],anima_model_mutable_copy,personModel_mutable_copy_array,personModel_mutable_copy_array.array[1]);
NSLog(@" 打印信息修改后array:%p+%@\ncopyArray:%p+%@\nmutableCopyArray:%p+%@",array,array,copyArray,copyArray,mutableCopyArray,mutableCopyArray);
打印信息
打印信息的排版有点乱,这不是重点,通过观察,我们可以发现数组中模型中的数组中的模型都是进行了完全拷贝
,看最后一行打印信息,前面两个数组中的address
都和第一行中的不一样,因为我们把地址改成了阆中
,而最后一个数组中的address
却还保持着之前的值,这说明,我们是完成了完全拷贝的
上面我用的是NSArray
进行的举例,而其他集合类型NSDictionary
和NSSet
的Category
方法也大同小异
相关代码,请移步demo如果觉得还可以,可以给个star
哦,不甚感激~