這幾天公司上線一個項目,改bug過程中,就遇到一個數(shù)組拷貝問題翎猛,廢了半天勁兒才解決掉,特此詳細(xì)研究了一下接剩。其場景大概如下:
A數(shù)組中存放著好多個自定義模型Person切厘,Person模型中又有一個數(shù)組屬性modelArray,modelArray數(shù)組又包含另外一個模型Son懊缺。此時疫稿,需要將A數(shù)組元素拷貝到B數(shù)組中,并且修改B數(shù)組中元素桐汤,不能影響到A數(shù)組而克。
首先靶壮,先不管上面的場景如何解決怔毛,因為文章末尾會給出具體解決方案。這里我們將由淺入深腾降,從深拷貝和淺拷貝概念拣度,到簡單數(shù)組元素拷貝,再到模型數(shù)組元素拷貝,逐步進(jìn)行分析抗果。
一筋帖、深拷貝和淺拷貝概念
這里引入官方到一段話:
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.
大致意思是:
共有兩種類型的對象拷貝:淺拷貝和深拷貝。普通拷貝是淺拷貝冤馏,它生成一個新集合日麸,該集合與原有集合共同持有對象。深拷貝會從原有集合中生成新的對象逮光,并把這些對象添加到新的集合中代箭。
簡而言之,淺拷貝不會產(chǎn)生新的對象涕刚,深拷貝會產(chǎn)生新的對象嗡综。
二、裝有基本類型元素的數(shù)組拷貝
2.1杜漠、NSArray與copy
NSArray *normalArray = [[NSArray alloc] initWithObjects:@"1",@"2",@"3", nil];
id tempArray = [normalArray copy];
[tempArray isKindOfClass:[NSMutableArray class]] ? NSLog(@"tempArrayOne可變數(shù)組"):NSLog(@"tempArrayOne不可變數(shù)組");
打印斷點极景、查看結(jié)果如下:
結(jié)論:不可變數(shù)組進(jìn)行copy,不會開辟新的內(nèi)存空間驾茴,生成一個不可變對象盼樟,指向同一個數(shù)組。
2.2沟涨、NSMutableArray與copy
NSMutableArray *normalArray = [[NSMutableArray alloc] initWithObjects:@"1",@"2",@"3", nil];
id tempArray = [normalArray copy];
[tempArray isKindOfClass:[NSMutableArray class]] ? NSLog(@"tempArrayOne可變數(shù)組"):NSLog(@"tempArrayOne不可變數(shù)組");
normalArray[0] = @"1000";//修改數(shù)組元素
NSLog(@"normalArray內(nèi)存地址 = %p",normalArray);
NSLog(@"tempArrayOne內(nèi)存地址 = %p",tempArray);
NSLog(@"normalArray = %@",normalArray);
NSLog(@"tempArrayOne = %@",tempArray);
打印斷點恤批、查看結(jié)果如下:
結(jié)論:可變數(shù)組進(jìn)行copy,會開辟新的內(nèi)存空間裹赴,生成一個新的不可變數(shù)組喜庞,兩個數(shù)組之間不受任何影響。
2.3棋返、NSArray與mutableCopy
NSArray *normalArray = [[NSArray alloc] initWithObjects:@"1",@"2",@"3", nil];
id tempArray = [normalArray mutableCopy];
[tempArray isKindOfClass:[NSMutableArray class]] ? NSLog(@"tempArrayOne可變數(shù)組"):NSLog(@"tempArrayOne不可變數(shù)組");
tempArray[0] = @"1000";//改變數(shù)組
NSLog(@"normalArray內(nèi)存地址 = %p",normalArray);
NSLog(@"tempArrayOne內(nèi)存地址 = %p",tempArray);
NSLog(@"normalArray內(nèi)存地址 = %p",normalArray);
NSLog(@"tempArrayOne內(nèi)存地址 = %p",tempArray);
打印斷點延都、查看結(jié)果如下:
結(jié)論:不可變數(shù)組進(jìn)行mutableCopy,會開辟新的內(nèi)存空間睛竣,生成一個可變數(shù)組晰房、兩個數(shù)組之間相互不影響。
2.4射沟、NSMutableArray與mutableCopy
NSMutableArray *normalArray = [[NSMutableArray alloc] initWithObjects:@"1",@"2",@"3", nil];
id tempArrayOne = [normalArray mutableCopy];
[tempArrayOne isKindOfClass:[NSMutableArray class]] ? NSLog(@"tempArrayOne可變數(shù)組"):NSLog(@"tempArrayOne不可變數(shù)組");
normalArray[0] = @"1000";
NSLog(@"normalArray = %@",normalArray);
NSLog(@"normalArray內(nèi)存地址 = %p",normalArray);
NSLog(@"tempArrayOne = %@",tempArrayOne);
NSLog(@"tempArrayOne內(nèi)存地址 = %p",tempArrayOne);
打印斷點殊者、查看結(jié)果如下:
結(jié)論:可變數(shù)組進(jìn)行mutableCopy,會開辟新的內(nèi)存空間验夯、生成一個可變數(shù)組猖吴、兩個數(shù)組之間相互不影響。
小結(jié):
針對上述情況挥转,在網(wǎng)上找到一個繪制好的列表海蔽,總結(jié)的很好共屈,這里直接借用一下,在此謝過党窜。
三拗引、裝有模型元素的數(shù)組拷貝
這里可以明確告訴大家,數(shù)組仍然遵循上述規(guī)則幌衣,但是模型是不拷貝的矾削,要不然也不用這么費勁兒寫這篇文章了。
這里我們用裝有模型的可變數(shù)組的mutableCopy進(jìn)行驗證豁护,代碼如下:
//1怔软、初始數(shù)組
Person *onePerson = [[Person alloc] init];
onePerson.name = @"onePerson";
onePerson.age = 1;
NSMutableArray *normalModelArray = [[NSMutableArray alloc] init];
[normalModelArray addObject:onePerson];
//2、拷貝數(shù)組
id tempModelArrayOne = [normalModelArray mutableCopy];
[tempModelArrayOne isKindOfClass:[NSMutableArray class]] ? NSLog(@"tempArrayOne可變數(shù)組"):NSLog(@"tempArrayOne不可變數(shù)組");
//3择镇、打印模型信息
NSLog(@"normalArray = %@",normalModelArray);
NSLog(@"tempArrayOne = %@",tempModelArrayOne);
NSLog(@"normalArray內(nèi)存地址 = %p",normalModelArray);
NSLog(@"tempArrayOne內(nèi)存地址 = %p",tempModelArrayOne);
打印斷點挡逼、查看結(jié)果如下:
結(jié)論:數(shù)組拷貝仍然遵循上面的規(guī)則,但是里面的模型是同一個對象腻豌,也就是說對象沒有進(jìn)行深拷貝家坎。
四、模型深度拷貝最終解決方案
4.1吝梅、方案介紹
數(shù)組里面的模型不能拷貝虱疏,我們該怎么辦?放心苏携,官方已經(jīng)給出答案:
There are two ways to make deep copies of a collection. You can use the collection’s equivalent of
initWithArray:copyItems:
withYES
as the second parameter. If you create a deep copy of a collection in this way, each object in the collection is sent acopyWithZone:
message. If the objects in the collection have adopted theNSCopying
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 theNSCopying
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 ofinitWithArray:copyItems:
withYES
as the second parameter.
If you need atrue
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 theNSCoding
protocol. An example of this technique is shown in Listing 3.
Listing 3 A true deep copy
NSArray* trueDeepCopyArray = [NSKeyedUnarchiver unarchiveObjectWithData:[NSKeyedArchiver archivedDataWithRootObject:oldArray]];
大致意思如下:
這里有兩種方法可以實現(xiàn)深拷貝做瞪,第一種方法是initWithArray:copyItems: with YES as the second parameter
。用這個方法進(jìn)行拷貝右冻,集合里面的模型就要實現(xiàn)copyWithZone:
方法装蓬;如果不實現(xiàn)的話,就會報運行時錯誤纱扭。因為copyWithZone:
是淺拷貝牍帚,所以這里只會對模型的基本屬性進(jìn)行拷貝。換句話說乳蛾,模型本身的屬性都會進(jìn)行深拷貝暗赶,但是如果模型屬性還包含模型,那這個方法就無濟于事了肃叶。此時蹂随,只能用NSArray* trueDeepCopyArray = [NSKeyedUnarchiver unarchiveObjectWithData:[NSKeyedArchiver archivedDataWithRootObject:oldArray]];
。
4.2因惭、方案實踐
看了官方的介紹岳锁,貌似有點茅塞頓開的感覺,可是不試一試怎么能夠知道呢筛欢。
這里我們將從三個方面對兩個方法進(jìn)行對比分析浸锨。
4.2.1、initWithArray:copyItems
- 1>基本模型
代碼如下:
//1版姑、初始數(shù)組
Person *onePerson = [[Person alloc] init];
onePerson.name = @"onePerson";
NSMutableArray *normalModelArray = [[NSMutableArray alloc] init];
[normalModelArray addObject:onePerson];
//2柱搜、拷貝數(shù)組
id tempModelArrayOne = [[NSMutableArray alloc] initWithArray:normalModelArray copyItems:YES];
[tempModelArrayOne isKindOfClass:[NSMutableArray class]] ? NSLog(@"tempArrayOne可變數(shù)組"):NSLog(@"tempArrayOne不可變數(shù)組");
//3、改變其中一個元素信息
onePerson.name = @"我改變了哈";
//4剥险、打印模型信息
NSLog(@"normalArray = %@",normalModelArray);
NSLog(@"tempArrayOne = %@",tempModelArrayOne);
NSLog(@"normalArray內(nèi)存地址 = %p",normalModelArray);
NSLog(@"tempArrayOne內(nèi)存地址 = %p",tempModelArrayOne);
打印結(jié)果如下:
結(jié)論:生成新的Person模型聪蘸,開辟新的存儲空間,Person模型基本元素相互不影響表制。
- 2>模型中含有模型
代碼如下:
//1健爬、初始數(shù)組
Person *onePerson = [[Person alloc] init];
onePerson.name = @"onePerson";
onePerson.son.name = @"張三";
NSMutableArray *normalModelArray = [[NSMutableArray alloc] init];
[normalModelArray addObject:onePerson];
//2、拷貝數(shù)組
id tempModelArrayOne = [[NSMutableArray alloc] initWithArray:normalModelArray copyItems:YES];
[tempModelArrayOne isKindOfClass:[NSMutableArray class]] ? NSLog(@"tempArrayOne可變數(shù)組"):NSLog(@"tempArrayOne不可變數(shù)組");
//3么介、改變其中一個元素信息
onePerson.name = @"我改變了哈";
onePerson.son.name = @"李四";
//4娜遵、打印模型信息
NSLog(@"normalArray = %@",normalModelArray);
NSLog(@"tempArrayOne = %@",tempModelArrayOne);
NSLog(@"normalArray內(nèi)存地址 = %p",normalModelArray);
NSLog(@"tempArrayOne內(nèi)存地址 = %p",tempModelArrayOne);
打印結(jié)果如下:
結(jié)論:生成新的Son模型,開辟新的存儲空間壤短,Son模型基本元素相互不影響设拟。
- 3>模型中的數(shù)組屬性含有模型
代碼如下:
//1、初始數(shù)組
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久脯、拷貝數(shù)組
id tempModelArrayOne = [[NSMutableArray alloc] initWithArray:normalModelArray copyItems:YES];
[tempModelArrayOne isKindOfClass:[NSMutableArray class]] ? NSLog(@"tempArrayOne可變數(shù)組"):NSLog(@"tempArrayOne不可變數(shù)組");
//3纳胧、改變其中一個元素信息
son.name = @"改變兒子";
//4、打印模型信息
NSLog(@"normalArray = %@",normalModelArray);
NSLog(@"tempArrayOne = %@",tempModelArrayOne);
NSLog(@"normalArray內(nèi)存地址 = %p",normalModelArray);
NSLog(@"tempArrayOne內(nèi)存地址 = %p",tempModelArrayOne);
打印結(jié)果如下:
結(jié)論:主模型數(shù)組中的模型不會開辟新的內(nèi)存空間帘撰,仍然是同一個對象跑慕。
4.2.2、歸檔和解檔
- 1>基本模型
//1摧找、初始數(shù)組
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可變數(shù)組"):NSLog(@"tempArrayOne不可變數(shù)組");
//3、改變其中一個元素信息
onePerson.name = @"我改變了哈";
//4蹬耘、打印模型信息
NSLog(@"normalArray = %@",normalModelArray); NSLog(@"tempArrayOne = %@",tempModelArrayOne);
NSLog(@"normalArray內(nèi)存地址 = %p",normalModelArray);
NSLog(@"tempArrayOne內(nèi)存地址 = %p",tempModelArrayOne);
打印結(jié)果:
結(jié)論:生成新的Person模型钮科,開辟新的存儲空間,Person模型基本元素相互不影響婆赠。
- 2>模型中含有模型
代碼如下:
//1绵脯、初始數(shù)組
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可變數(shù)組"):NSLog(@"tempArrayOne不可變數(shù)組");
//3休里、改變其中一個元素信息
onePerson.name = @"我改變了哈";
onePerson.son.name = @"李四";
//4蛆挫、打印模型信息
NSLog(@"normalArray = %@",normalModelArray);
NSLog(@"tempArrayOne = %@",tempModelArrayOne);
NSLog(@"normalArray內(nèi)存地址 = %p",normalModelArray);
NSLog(@"tempArrayOne內(nèi)存地址 = %p",tempModelArrayOne);
打印結(jié)果如下:
結(jié)論:生成新的Son模型,開辟新的存儲空間妙黍,Son模型基本元素相互不影響悴侵。
- 3>模型中的數(shù)組屬性含有模型
代碼如下:
//1、初始數(shù)組
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可變數(shù)組"):NSLog(@"tempArrayOne不可變數(shù)組");
//3可免、改變其中一個元素信息
son.name = @"改變兒子";
//4抓于、打印模型信息
NSLog(@"normalArray = %@",normalModelArray);
NSLog(@"tempArrayOne = %@",tempModelArrayOne);
NSLog(@"normalArray內(nèi)存地址 = %p",normalModelArray);
NSLog(@"tempArrayOne內(nèi)存地址 = %p",tempModelArrayOne);
打印結(jié)果:
結(jié)論:主模型數(shù)組中的模型會開辟新的內(nèi)存空間,模型之間相互不影響浇借。
五捉撮、總結(jié)
5.1、方案
通過initWithArray:copyItems
和歸檔解檔
可以對模型進(jìn)行拷貝妇垢,具體如下:
//方案1
NSArray *deepCopyArray=[[NSArray alloc] initWithArray:someArray copyItems:YES];
//方案2
NSArray* trueDeepCopyArray = [NSKeyedUnarchiver unarchiveObjectWithData:[NSKeyedArchiver archivedDataWithRootObject:oldArray]];
使用initWithArray:copyItems
需要模型遵守NSCopying或NSMutableCopying協(xié)議并重寫以下方法:
- (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協(xié)議并重寫以下方法:
- (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"];
}
備注: 如果模型中有嵌套子模型巾遭,子模型也需要實現(xiàn)上述方法,否則會報運行時錯誤闯估。
5.2灼舍、方案區(qū)別
二者都可以對模型進(jìn)行深拷貝,但是initWithArray:copyItems
只能對一級模型進(jìn)行深拷貝涨薪,也就是模型中含有數(shù)組模型骑素,它就無能為力了。而利用歸檔和解檔
則不存在這樣問題刚夺,無論模型嵌套多少層砂豌。