iOS 模型数组拷贝深度解析

这几天公司上线一个项目,改bug过程中,就遇到一个数组拷贝问题,废了半天劲儿才解决掉,特此详细研究了一下。其场景大概如下:

A数组中存放着好多个自定义模型Person,Person模型中又有一个数组属性modelArray,modelArray数组又包含另外一个模型Son。此时,需要将A数组元素拷贝到B数组中,并且修改B数组中元素,不能影响到A数组。

首先,先不管上面的场景如何解决,因为文章末尾会给出具体解决方案。这里我们将由浅入深,从深拷贝和浅拷贝概念,到简单数组元素拷贝,再到模型数组元素拷贝,逐步进行分析。

一、深拷贝和浅拷贝概念

这里引入官方到一段话:

There are two kinds of object copying: shallow copies and deep copies. The normal copy is a shallow copy that produces a new collection that shares ownership of the objects with the original. Deep copies create new objects from the originals and add those to the new collection.

大致意思是:
共有两种类型的对象拷贝:浅拷贝和深拷贝。普通拷贝是浅拷贝,它生成一个新集合,该集合与原有集合共同持有对象。深拷贝会从原有集合中生成新的对象,并把这些对象添加到新的集合中。
简而言之,浅拷贝不会产生新的对象,深拷贝会产生新的对象

图1 Shallow copies and deep copies

二、装有基本类型元素的数组拷贝

2.1、NSArray与copy

NSArray *normalArray = [[NSArray alloc] initWithObjects:@"1",@"2",@"3", nil];
id tempArray = [normalArray copy];
[tempArray isKindOfClass:[NSMutableArray class]] ? NSLog(@"tempArrayOne可变数组"):NSLog(@"tempArrayOne不可变数组"); 

打印断点、查看结果如下:


图2

结论:不可变数组进行copy,不会开辟新的内存空间,生成一个不可变对象,指向同一个数组

2.2、NSMutableArray与copy

NSMutableArray *normalArray = [[NSMutableArray alloc] initWithObjects:@"1",@"2",@"3", nil];
id tempArray = [normalArray copy];
[tempArray isKindOfClass:[NSMutableArray class]] ? NSLog(@"tempArrayOne可变数组"):NSLog(@"tempArrayOne不可变数组");
normalArray[0] = @"1000";//修改数组元素
NSLog(@"normalArray内存地址 = %p",normalArray);
NSLog(@"tempArrayOne内存地址 = %p",tempArray);
NSLog(@"normalArray = %@",normalArray);
NSLog(@"tempArrayOne = %@",tempArray);

打印断点、查看结果如下:


图3

结论:可变数组进行copy,会开辟新的内存空间,生成一个新的不可变数组,两个数组之间不受任何影响

2.3、NSArray与mutableCopy

NSArray *normalArray = [[NSArray alloc] initWithObjects:@"1",@"2",@"3", nil];
id tempArray = [normalArray mutableCopy];
[tempArray isKindOfClass:[NSMutableArray class]] ? NSLog(@"tempArrayOne可变数组"):NSLog(@"tempArrayOne不可变数组");
tempArray[0] = @"1000";//改变数组
NSLog(@"normalArray内存地址 = %p",normalArray);
NSLog(@"tempArrayOne内存地址 = %p",tempArray);
NSLog(@"normalArray内存地址 = %p",normalArray);
NSLog(@"tempArrayOne内存地址 = %p",tempArray); 

打印断点、查看结果如下:

图4

结论:不可变数组进行mutableCopy,会开辟新的内存空间,生成一个可变数组、两个数组之间相互不影响

2.4、NSMutableArray与mutableCopy

NSMutableArray *normalArray = [[NSMutableArray alloc] initWithObjects:@"1",@"2",@"3", nil];
id tempArrayOne = [normalArray mutableCopy];
[tempArrayOne isKindOfClass:[NSMutableArray class]] ? NSLog(@"tempArrayOne可变数组"):NSLog(@"tempArrayOne不可变数组");
normalArray[0] = @"1000";
NSLog(@"normalArray = %@",normalArray);
NSLog(@"normalArray内存地址 = %p",normalArray);
NSLog(@"tempArrayOne = %@",tempArrayOne);
NSLog(@"tempArrayOne内存地址 = %p",tempArrayOne); 

打印断点、查看结果如下:


图5

结论:可变数组进行mutableCopy,会开辟新的内存空间、生成一个可变数组、两个数组之间相互不影响

小结:
针对上述情况,在网上找到一个绘制好的列表,总结的很好,这里直接借用一下,在此谢过。

图6

三、装有模型元素的数组拷贝

这里可以明确告诉大家,数组仍然遵循上述规则,但是模型是不拷贝的,要不然也不用这么费劲儿写这篇文章了。
这里我们用装有模型的可变数组的mutableCopy进行验证,代码如下:

//1、初始数组
Person *onePerson = [[Person alloc] init];
onePerson.name = @"onePerson";
onePerson.age = 1;
NSMutableArray *normalModelArray = [[NSMutableArray alloc] init];
[normalModelArray addObject:onePerson];
//2、拷贝数组
id tempModelArrayOne = [normalModelArray mutableCopy];
[tempModelArrayOne isKindOfClass:[NSMutableArray class]] ? NSLog(@"tempArrayOne可变数组"):NSLog(@"tempArrayOne不可变数组");
//3、打印模型信息
NSLog(@"normalArray = %@",normalModelArray);
NSLog(@"tempArrayOne = %@",tempModelArrayOne);
NSLog(@"normalArray内存地址 = %p",normalModelArray);
NSLog(@"tempArrayOne内存地址 = %p",tempModelArrayOne);

打印断点、查看结果如下:


图7

结论:数组拷贝仍然遵循上面的规则,但是里面的模型是同一个对象,也就是说对象没有进行深拷贝

四、模型深度拷贝最终解决方案

4.1、方案介绍

数组里面的模型不能拷贝,我们该怎么办?放心,官方已经给出答案:

There are two ways to make deep copies of a collection. You can use the collection’s equivalent of initWithArray:copyItems: with YES as the second parameter. If you create a deep copy of a collection in this way, each object in the collection is sent a copyWithZone: message. If the objects in the collection have adopted the NSCopying protocol, the objects are deeply copied to the new collection, which is then the sole owner of the copied objects. If the objects do not adopt the NSCopying protocol, attempting to copy them in such a way results in a runtime error. However, copyWithZone: produces a shallow 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.
Listing 2 Making a deep copy
NSArray *deepCopyArray = [[NSArray alloc]initWithArray:someArray copyItems:YES];
This technique applies to the other collections as well. Use the collection’s equivalent of initWithArray:copyItems: with YES as the second parameter.
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.
Listing 3 A true deep copy
NSArray* trueDeepCopyArray = [NSKeyedUnarchiver unarchiveObjectWithData:[NSKeyedArchiver archivedDataWithRootObject:oldArray]];

大致意思如下:
这里有两种方法可以实现深拷贝,第一种方法是initWithArray:copyItems: with YES as the second parameter。用这个方法进行拷贝,集合里面的模型就要实现copyWithZone:方法;如果不实现的话,就会报运行时错误。因为copyWithZone:是浅拷贝,所以这里只会对模型的基本属性进行拷贝。换句话说,模型本身的属性都会进行深拷贝,但是如果模型属性还包含模型,那这个方法就无济于事了。此时,只能用NSArray* trueDeepCopyArray = [NSKeyedUnarchiver unarchiveObjectWithData:[NSKeyedArchiver archivedDataWithRootObject:oldArray]];

4.2、方案实践

看了官方的介绍,貌似有点茅塞顿开的感觉,可是不试一试怎么能够知道呢。
这里我们将从三个方面对两个方法进行对比分析。

4.2.1、initWithArray:copyItems

  • 1>基本模型
    代码如下:
//1、初始数组
Person *onePerson = [[Person alloc] init];
onePerson.name = @"onePerson";
NSMutableArray *normalModelArray = [[NSMutableArray alloc] init];
[normalModelArray addObject:onePerson];
//2、拷贝数组
id tempModelArrayOne = [[NSMutableArray alloc] initWithArray:normalModelArray copyItems:YES];
[tempModelArrayOne isKindOfClass:[NSMutableArray class]] ? NSLog(@"tempArrayOne可变数组"):NSLog(@"tempArrayOne不可变数组");
//3、改变其中一个元素信息
onePerson.name = @"我改变了哈";
//4、打印模型信息
NSLog(@"normalArray = %@",normalModelArray);
NSLog(@"tempArrayOne = %@",tempModelArrayOne);
NSLog(@"normalArray内存地址 = %p",normalModelArray);
NSLog(@"tempArrayOne内存地址 = %p",tempModelArrayOne); 

打印结果如下:


图8 copyItems(基本元素)

结论:生成新的Person模型,开辟新的存储空间,Person模型基本元素相互不影响

  • 2>模型中含有模型
    代码如下:
 //1、初始数组
Person *onePerson = [[Person alloc] init];
onePerson.name = @"onePerson";
onePerson.son.name = @"张三";
NSMutableArray *normalModelArray = [[NSMutableArray alloc] init];
[normalModelArray addObject:onePerson];
//2、拷贝数组
id tempModelArrayOne = [[NSMutableArray alloc] initWithArray:normalModelArray copyItems:YES];
[tempModelArrayOne isKindOfClass:[NSMutableArray class]] ? NSLog(@"tempArrayOne可变数组"):NSLog(@"tempArrayOne不可变数组");
//3、改变其中一个元素信息
onePerson.name = @"我改变了哈";
onePerson.son.name = @"李四";
//4、打印模型信息
NSLog(@"normalArray = %@",normalModelArray);
NSLog(@"tempArrayOne = %@",tempModelArrayOne);
NSLog(@"normalArray内存地址 = %p",normalModelArray);
NSLog(@"tempArrayOne内存地址 = %p",tempModelArrayOne);

打印结果如下:


图9 copyItems(模型中的模型).png

结论:生成新的Son模型,开辟新的存储空间,Son模型基本元素相互不影响

  • 3>模型中的数组属性含有模型
    代码如下:
//1、初始数组
Person *onePerson = [[Person alloc] init];
onePerson.name = @"onePerson";
Son *son = [[Son alloc] init];
son.name = @"儿子";
[onePerson.modelArray addObject:son];
NSMutableArray *normalModelArray = [[NSMutableArray alloc] init];
[normalModelArray addObject:onePerson];
//2、拷贝数组
id tempModelArrayOne = [[NSMutableArray alloc] initWithArray:normalModelArray copyItems:YES];
[tempModelArrayOne isKindOfClass:[NSMutableArray class]] ? NSLog(@"tempArrayOne可变数组"):NSLog(@"tempArrayOne不可变数组");
//3、改变其中一个元素信息
son.name = @"改变儿子";
//4、打印模型信息
NSLog(@"normalArray = %@",normalModelArray);
NSLog(@"tempArrayOne = %@",tempModelArrayOne);
NSLog(@"normalArray内存地址 = %p",normalModelArray);
NSLog(@"tempArrayOne内存地址 = %p",tempModelArrayOne); 

打印结果如下:


图10 copyItems(模型中的数组).png

结论:主模型数组中的模型不会开辟新的内存空间,仍然是同一个对象

4.2.2、归档和解档

  • 1>基本模型
//1、初始数组
Person *onePerson = [[Person alloc] init];
onePerson.name = @"onePerson";
NSMutableArray *normalModelArray = [[NSMutableArray alloc] init];
[normalModelArray addObject:onePerson];
//2、归档和解档
NSArray* trueDeepCopyArray = [NSKeyedUnarchiver unarchiveObjectWithData:[NSKeyedArchiver archivedDataWithRootObject:normalModelArray]];
id tempModelArrayOne = [[NSMutableArray alloc] initWithArray:trueDeepCopyArray copyItems:YES];
[tempModelArrayOne isKindOfClass:[NSMutableArray class]] ? NSLog(@"tempArrayOne可变数组"):NSLog(@"tempArrayOne不可变数组");
//3、改变其中一个元素信息
onePerson.name = @"我改变了哈";
//4、打印模型信息
NSLog(@"normalArray = %@",normalModelArray);    NSLog(@"tempArrayOne = %@",tempModelArrayOne);
NSLog(@"normalArray内存地址 = %p",normalModelArray);
NSLog(@"tempArrayOne内存地址 = %p",tempModelArrayOne); 

打印结果:


图11 归档(基本元素).png

结论:生成新的Person模型,开辟新的存储空间,Person模型基本元素相互不影响

  • 2>模型中含有模型
    代码如下:
//1、初始数组
Person *onePerson = [[Person alloc] init];
onePerson.name = @"onePerson";
onePerson.son.name = @"张三";
NSMutableArray *normalModelArray = [[NSMutableArray alloc] init];
[normalModelArray addObject:onePerson];
//2、归档和接档
NSArray* trueDeepCopyArray = [NSKeyedUnarchiver unarchiveObjectWithData:[NSKeyedArchiver archivedDataWithRootObject:normalModelArray]];
id tempModelArrayOne = [[NSMutableArray alloc] initWithArray:trueDeepCopyArray copyItems:YES];
[tempModelArrayOne isKindOfClass:[NSMutableArray class]] ? NSLog(@"tempArrayOne可变数组"):NSLog(@"tempArrayOne不可变数组");
//3、改变其中一个元素信息
onePerson.name = @"我改变了哈";
onePerson.son.name = @"李四";
//4、打印模型信息
NSLog(@"normalArray = %@",normalModelArray);
NSLog(@"tempArrayOne = %@",tempModelArrayOne);
NSLog(@"normalArray内存地址 = %p",normalModelArray);
NSLog(@"tempArrayOne内存地址 = %p",tempModelArrayOne);

打印结果如下:


图12 归档(模型中的模型).png

结论:生成新的Son模型,开辟新的存储空间,Son模型基本元素相互不影响

  • 3>模型中的数组属性含有模型
    代码如下:
//1、初始数组
Person *onePerson = [[Person alloc] init];
onePerson.name = @"onePerson";
Son *son = [[Son alloc] init];
son.name = @"儿子";
[onePerson.modelArray addObject:son];
NSMutableArray *normalModelArray = [[NSMutableArray alloc] init];
[normalModelArray addObject:onePerson];
//2、归档和解档
NSArray* trueDeepCopyArray = [NSKeyedUnarchiver unarchiveObjectWithData:[NSKeyedArchiver archivedDataWithRootObject:normalModelArray]];
id tempModelArrayOne = [[NSMutableArray alloc] initWithArray:trueDeepCopyArray copyItems:YES];
[tempModelArrayOne isKindOfClass:[NSMutableArray class]] ? NSLog(@"tempArrayOne可变数组"):NSLog(@"tempArrayOne不可变数组");
//3、改变其中一个元素信息
son.name = @"改变儿子";
//4、打印模型信息
NSLog(@"normalArray = %@",normalModelArray);
NSLog(@"tempArrayOne = %@",tempModelArrayOne);
NSLog(@"normalArray内存地址 = %p",normalModelArray);
NSLog(@"tempArrayOne内存地址 = %p",tempModelArrayOne);

打印结果:


图13 归档(模型中的数组).png

结论:主模型数组中的模型会开辟新的内存空间,模型之间相互不影响

五、总结

5.1、方案

通过initWithArray:copyItems归档解档可以对模型进行拷贝,具体如下:

//方案1
NSArray *deepCopyArray=[[NSArray alloc] initWithArray:someArray copyItems:YES];
//方案2
NSArray* trueDeepCopyArray = [NSKeyedUnarchiver unarchiveObjectWithData:[NSKeyedArchiver archivedDataWithRootObject:oldArray]];

使用initWithArray:copyItems需要模型遵守NSCopying或NSMutableCopying协议并重写以下方法:

- (id)copyWithZone:(NSZone *)zone
{
    Person *person = [[[self class] allocWithZone:zone] init];
    person.name = [self.name copy];
    person.age = self.age;
    person.son = [self.son copy];
    person.modelArray = [self.modelArray copy];
    return person;
}

- (id)mutableCopyWithZone:(NSZone *)zone
{
    Person *person = [[[self class] allocWithZone:zone] init];
    person.name = [self.name mutableCopy];
    person.age = self.age;
    person.son = [self.son mutableCopy];
    person.modelArray = [self.modelArray mutableCopy];
    return person;
}

使用归档和解档需要模型遵守NSCoding协议并重写以下方法:

- (id)initWithCoder: (NSCoder *)coder
{
    if (self = [super init])
    {
        self.name = [coder decodeObjectForKey:@"name"];
        self.age = (int)[coder decodeIntegerForKey:@"age"];
        self.son = [coder decodeObjectForKey:@"son"];
        self.modelArray = [coder decodeObjectForKey:@"modelArray"];
    }
    return self;
}

- (void) encodeWithCoder: (NSCoder *)coder
{
    [coder encodeObject:self.name forKey:@"name"];
    [coder encodeInteger:self.age forKey:@"age"];
    [coder encodeObject:self.son forKey:@"son"];
    [coder encodeObject:self.modelArray forKey:@"modelArray"];
}

备注: 如果模型中有嵌套子模型,子模型也需要实现上述方法,否则会报运行时错误。

5.2、方案区别

二者都可以对模型进行深拷贝,但是initWithArray:copyItems只能对一级模型进行深拷贝,也就是模型中含有数组模型,它就无能为力了。而利用归档和解档则不存在这样问题,无论模型嵌套多少层。

参考网址:

苹果官方文档

iOS 图文并茂的带你了解深拷贝与浅拷贝

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

推荐阅读更多精彩内容

  • 今天来说一下iOS中的copy。 在iOS中,拷贝有两种方式,深拷贝(Deep copy)和浅拷贝(Shallow...
    张囧瑞阅读 935评论 0 0
  • Swift1> Swift和OC的区别1.1> Swift没有地址/指针的概念1.2> 泛型1.3> 类型严谨 对...
    cosWriter阅读 11,065评论 1 32
  • 1、对象拷贝有两种方式:浅复制和深复制。顾名思义,浅复制,并不拷贝对象本身,仅仅是拷贝指向对象的指针;深复制是直接...
    滴答大阅读 756评论 0 2
  • 本文为转载: 作者:zyydeveloper 链接:http://www.jianshu.com/p/5f776a...
    Buddha_like阅读 844评论 0 2
  • 概念 在Objective-C中并不是所有的对象都支持Copy,MutableCopy,遵守NSCopying协议...
    LeoAu阅读 8,667评论 10 28