iOS中注意使用for..in同时读取和修改数组导致crash

问题 在工作中遇到数组闪退的问题:
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。
解决方法
  1. 读取是用新创建的数组
    即把原先的数组新创建一份,用于遍历,修改时操作原先的数组。
//避免同时修改和读取数组导致闪退 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]; } } }

  1. 使用枚举的方式来遍历和修改数组
[self.dataArray enumerateObjectsUsingBlock:^(idobj, NSUInteger idx, BOOL * stop) { NSLog(@"item:%@,index:%@:",obj,@(idx)); if ([obj isEqualToString:@"one"]) { NSLog(@"removeitem:%@,removeindex:%@:",obj,@(idx)); [self.dataArray removeObject:obj]; * stop = YES; } }];

  1. 使用遍历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循环同时读取和操作同一个数组会出现崩溃呢?
【iOS中注意使用for..in同时读取和修改数组导致crash】因为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); }

    推荐阅读