问题
在工作中遇到数组闪退的问题:
crash信息:
'NSGenericException', reason: '*** Collection <__NSArrayM: 0x1cc0573a0> was mutated while being enumerated.'
*** First throw call stack:
中文的意思就是,在数组被枚举时发生突变,o(╥﹏╥)o
出现这种问题是因为在遍历时修改了数组原来数组及在同一时间,不同的线程同时读取和修改了数组。经实践,一般会出现在for-in循环遍历并操作数组中。而在传统的for循环中不会出现。
项目中原先有问题的代码:
for (PersonList *person in self.selectedMemberAry) {
for (MissiveParticipantVO *participantVO in self.mustSelectedMemberAry) {
if ([person.iObjectId isEqualToString:participantVO.uniquneId]) {
[self.selectedMemberAry removeObject:person];
//[self.treeData.selectedOriginalVOS removeObject:person];
}
}
}
这里同时读取和修改selectedMemberAry数组中的数据,所以才出现了crash。
解决方法
- 读取是用新创建的数组
即把原先的数组新创建一份,用于遍历,修改时操作原先的数组。
//避免同时修改和读取数组导致闪退
NSArray *tempArray = [NSArray arrayWithArray:self.selectedMemberAry];
for (EissiveParticipantVO *participant in originalVOS) {
participant.mustSelected = YES;
[mustSelectedArray addObject:participant];
for (MissiveParticipantVO *selectedParticipant in tempArray) {
if ([[participant uniquneId] isEqualToString:[selectedParticipant uniquneId]]) {
[self.selectedMemberAry removeObject:selectedParticipant];
}
}
}
- 使用枚举的方式来遍历和修改数组
[self.dataArray enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL * stop) {
NSLog(@"item:%@,index:%@:",obj,@(idx));
if ([obj isEqualToString:@"one"]) {
NSLog(@"removeitem:%@,removeindex:%@:",obj,@(idx));
[self.dataArray removeObject:obj];
* stop = YES;
}
}];
- 使用遍历for循环
//使用这种方式居然是不会有问题的
for (NSInteger i = 0; i < self.dataArray.count; i++) {
NSString *item = [self.dataArray objectAtIndex:i];
NSLog(@"数组元素:%@",item);
if ([item isEqualToString:@"one"]) {
[self.dataArray removeObject:item];
}
}
Why?
为什么使用for..in循环同时读取和操作同一个数组会出现崩溃呢?
因为for...in...利用了快速枚举NSFastEnumerate
,其在内部是用iterator(迭代器)
实现遍历的。
NSArray的枚举操作中有一条需要注意:对于可变数组进行枚举操作时,你不能通过添加或删除对象这类操作来改变数组容器。如果你这么做了,枚举器会很困惑,而你将得到未定义的结果。
而且本身这种操作也是有问题的,数组容器已经改变,可能便利到没有分配的位置,用for循环机器不能自己察觉,但是枚举器可以察觉.
当只有一个元素时或删除最后一个元素时不会报错,因为这时已经遍历完了。
当删除和修改数组中元素时,建议反向遍历进行操作
。
当我们正向遍历删除数组中的元素时,删除一个元素(如索引2)后,其后一个元素(索引3)就会往前移动,其索引就变成了2,而下次循环将从3开始,所以这个元素就会被略过,导致判断出现问题。所以我们应该从后往前遍历数组进行删除和修改其中的元素。
//["one","two","three","four","five"]
for (NSInteger i = 0; i < self.dataArray.count; i++) {
NSString *item = [self.dataArray objectAtIndex:i];
if ([item isEqualToString:@"three"]) {
[self.dataArray removeObjectAtIndex:i];
}
NSLog(@"数组元素:%@--%@",@(i),item);
/**
* 打印:
数组元素:0--one
数组元素:1--two
数组元素:2--three 应该是four
数组元素:3--five
**/
}
//从后往前遍历
for (NSInteger j = self.dataArray.count - 1; j >= 0; j--) {
NSString *item = [self.dataArray objectAtIndex:j];
if ([item isEqualToString:@"three"]) {
[self.dataArray removeObjectAtIndex:j];
}
NSLog(@"数组元素:%@--%@",@(j),item);
}