君不見,高堂明鏡悲白發(fā),朝如青絲暮成雪
前記
最近在項目中,用到了關(guān)于copy的問題,當(dāng)我對自己定義的模型進(jìn)行拷貝的時候,居然沒有實現(xiàn)想要的深拷貝,于是出了點小問題,讓我有點小郁悶,所以決定好好深入研究下copy與mutableCopy與NSDictionary、NSArray、model之間的小事~
copy 、mutableCopy
先來看看這兩個的區(qū)別吧
在分析區(qū)別之前,我們先上一段簡單的代碼
//非集合類測試copy 和 mutablecopy
NSString *string = @"abc";
NSString *copyString = [string copy];
//mutableCopy 后位動態(tài)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);
//打印結(jié)果
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:對我們的對象進(jìn)行拷貝,返回一個非動態(tài)的對象 -
mutableCopy:對我們的對象進(jìn)行拷貝,返回一個動態(tài)的對象
通過上圖,我們可以看到,雖然我們用的是NSString來接收[string mutableCopy],但是實際上得到的卻是一個NSMutableString類型的對象,這里不得不說一個比較嚴(yán)重的問題,就是如果我們在定義一個動態(tài)類型為@property (nonatomic,copy)屬性后,一定不能執(zhí)行copy操作,否則在你執(zhí)行修改動態(tài)類型內(nèi)容的時候,一定會crash,因為當(dāng)你copy后,你的動態(tài)類型已經(jīng)變成了一個不可變類型
下面我們來看上面貼的代碼,從代碼分析,我們可以看到,非集合類型和集合類型中的非動態(tài)類型,在copy后,地址還是一樣的,沒有變化,而除此之外,地址都發(fā)生了變化,我們可以得到下面的結(jié)論
- 非集合類型和集合類型非動態(tài)類型在
copy后,執(zhí)行的是淺拷貝(即對指針進(jìn)行拷貝) - 非集合類型和集合類型非動態(tài)類型在
mutableCopy后,執(zhí)行的是深拷貝(即對對象進(jìn)行拷貝),且返回動態(tài)類型 - 非集合類型和集合類型動態(tài)類型在
copy后,執(zhí)行的是深拷貝 - 非集合類型和集合類型動態(tài)類型在
mutableCopy后,執(zhí)行的是深拷貝,且返回動態(tài)類型
上面的結(jié)論也不是那么準(zhǔn)確,怎么說呢?容我慢慢分析
對于此處的深拷貝,我想大家一定想的是完完全全的拷貝吧,是的,我一開始也是這么想的,但是有沒有想過測試下,當(dāng)我們的集合有很多層的情況呢?下面就舉個例子來說明吧
代碼走起
@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
{
//測試加入數(shù)組中 對數(shù)組進(jìn)行拷貝
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);
//修改數(shù)組中的內(nèi)容
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);
}
//打印結(jié)果
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的地址,可以發(fā)現(xiàn),地址是發(fā)生了改變的,說明我們現(xiàn)在是執(zhí)行了深拷貝的,但是看打印信息修改后array這個位置,你會發(fā)現(xiàn),后面的內(nèi)容在修改后,居然變成一樣的了,但是我們明明修改的是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
是的,可以看到,三個數(shù)組里面的對象是同一個對象,也就是,數(shù)組雖然進(jìn)行了深拷貝,但是并沒有對里面的內(nèi)容進(jìn)行深拷貝,而是執(zhí)行的指針拷貝,瞬間有種懵逼的感覺,特么搞了這么久,還是不能執(zhí)行完全的深拷貝。所以,上面我們總結(jié)的4點中關(guān)于集合類型的深拷貝不能算是真正意義上的深拷貝,只能說是一種單層的深拷貝,即至少有一層是執(zhí)行了深拷貝的,但是在實際中,我們往往需要的是完完全全的完全深拷貝,那么針對集合類型,我們又該怎么來實現(xiàn)完全深拷貝呢?
集合的完全深拷貝
關(guān)于集合的完全深拷貝,這里有兩種方法
- 通過歸檔的方式來實現(xiàn)(后面會講到)
- 通過使用集合自帶的方法來實現(xiàn)(并不完美)
先來講講 這個并不完美的方法吧
以上面的例子來將,我們可以使用NSArray中的
- (instancetype)initWithArray:(NSArray<ObjectType> *)array copyItems:(BOOL)flag;
當(dāng)flag參數(shù)設(shè)置為YES的時候,集合里的每個對象都會去執(zhí)行copyWithZone方法,當(dāng)然這就必須要求我們的對象必須遵循 NSCopying協(xié)議,并且實現(xiàn)copyWithZone方法,此時對象就會被深復(fù)制到新的集合。如果對象沒有遵循 NSCopying 協(xié)議,而用這種方法進(jìn)行深拷貝,就會在運行時出錯
來實現(xiàn),當(dāng)然NSDictionary和NSSet也有對應(yīng)的方法
- (instancetype)initWithDictionary:(NSDictionary<KeyType, ObjectType> *)otherDictionary copyItems:(BOOL)flag;
- (instancetype)initWithSet:(NSSet<ObjectType> *)set copyItems:(BOOL)flag;
先看修改后的
{
//測試加入數(shù)組中 對數(shù)組進(jìn)行拷貝
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]);
//修改數(shù)組中的內(nèi)容
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);
}
//打印結(jié)果
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}"
)
從上面的結(jié)果,我們可以看出,最終實現(xiàn)了我們想要的效果,對數(shù)組實現(xià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
.
.
.
//實現(xiàn)部分
//測試加入數(shù)組中 對數(shù)組進(jìn)行拷貝
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]);
//修改數(shù)組中的內(nèi)容
PersonModel *personModel_array = array[0];
personModel_array.name = @"gao_修改";
personModel_array.age = 24;
Anima *anima_model = personModel_array.array[0];
anima_model.address = @"閬中";
//獲取深拷貝數(shù)組中的model中數(shù)組中的model
PersonModel *personModel_mutable_copy_array = mutableCopyArray[0];
Anima *anima_model_mutable_copy = personModel_mutable_copy_array.array[0];
NSLog(@" 打印信息獲取深拷貝數(shù)組中的model中數(shù)組中的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] 打印信息獲取深拷貝數(shù)組中的model中數(shù)組中的model地址:0x608000013100 ++ 0x608000013100
從上面的結(jié)果分析,當(dāng)層次一多,該方法也并不能完成我們想要的完全拷貝,那么我們除了用歸檔的方式,難道就沒有其他方式了么?答案肯定是 還有的,可以這么設(shè)想一下,集合無外乎就是裝了很多東西,既然是這樣,我們何必不針對集合里面的內(nèi)容層層進(jìn)行深拷貝呢?是的,我們可以通過遍歷的方式來進(jìn)行,為了方便使用,可以新建一個Category,于是乎就有了下面的代碼,我們先以NSArray為例
#import <Foundation/Foundation.h>
@interface NSArray (GLDeepCopy)
/**
返回當(dāng)前類型
@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];
//判斷該對象是否實現(xiàn)了NSCopying 協(xié)議的方法 如果是 則進(jìn)行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
在上面方法中,我們通過遍歷數(shù)組中的內(nèi)容來進(jìn)行循環(huán)拷貝,[object respondsToSelector:@selector(GLDeepCopy)],這一句是如果我們的對象實現(xiàn)了該方法,我們就去調(diào)用,比如當(dāng)前對象是數(shù)組,字典,NSSet或者是自定義數(shù)據(jù)模型,針對自定義數(shù)據(jù)模型,如果包含集合類型,而且又需要進(jìn)行完全拷貝的時候,最好實現(xiàn)GLDeepCopy方法,后面有例子。如果沒有集合類型,但是需要完全拷貝的話,就可以通過實現(xiàn)NSCopying或者NSMutableCopying協(xié)議來實現(xiàn)。
#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協(xié)議
- (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 == 深拷貝
//(如果沒有集合類型,可以不用執(zhí)行)
- (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 == 轉(zhuǎn)換
/**
將對象轉(zhuǎn)為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;
}
/**
針對對象里面屬性的不同屬性 進(jìn)行轉(zhuǎn)換
@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,而且我們希望實現(xiàn)完全拷貝,所以實現(xiàn)了GLDeepCopy,并且針對其中的NSMutableArray調(diào)用了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
由于該模型,沒有包含集合類型,所以我只是簡單的實現(xiàn)了NSCopying協(xié)議,下面來看使用情況
//測試加入數(shù)組中 對數(shù)組進(jìn)行拷貝
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]);
//修改數(shù)組中的內(nèi)容
PersonModel *personModel_array = array[0];
personModel_array.name = @"gao_修改";
personModel_array.age = 24;
Anima *anima_model = personModel_array.array[0];
anima_model.address = @"閬中";
//獲取深拷貝數(shù)組中的model中數(shù)組中的model
PersonModel *personModel_mutable_copy_array = mutableCopyArray[0];
Anima *anima_model_mutable_copy = personModel_mutable_copy_array.array[0];
NSLog(@" 打印信息獲取深拷貝數(shù)組中的model中數(shù)組中的model地址:\n原數(shù)組中的PersonModel中Array中的Anima:%p \n 原數(shù)組中的PersonModel中Array中的非Anima對象%p\n++ 深拷貝數(shù)組中的PersonModel中Array中的Anima:%p \n 深拷貝數(shù)組中的PersonModel:%p \n+ 深拷貝數(shù)組中的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);
打印信息

打印信息的排版有點亂,這不是重點,通過觀察,我們可以發(fā)現(xiàn)數(shù)組中模型中的數(shù)組中的模型都是進(jìn)行了完全拷貝,看最后一行打印信息,前面兩個數(shù)組中的address都和第一行中的不一樣,因為我們把地址改成了閬中,而最后一個數(shù)組中的address卻還保持著之前的值,這說明,我們是完成了完全拷貝的
上面我用的是NSArray進(jìn)行的舉例,而其他集合類型NSDictionary和NSSet的Category方法也大同小異
相關(guān)代碼,請移步demo如果覺得還可以,可以給個star哦,不甚感激~