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。
解决方法
- 读取是用新创建的数组
即把原先的数组新创建一份,用于遍历,修改时操作原先的数组。
//避免同时修改和读取数组导致闪退
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:^(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;
}
}];
- 使用遍历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);
}
推荐阅读
- 热闹中的孤独
- 放屁有这三个特征的,请注意啦!这说明你的身体毒素太多
- 20170612时间和注意力开销记录
- Shell-Bash变量与运算符
- JS中的各种宽高度定义及其应用
- 2021-02-17|2021-02-17 小儿按摩膻中穴-舒缓咳嗽
- 深入理解Go之generate
- 异地恋中,逐渐适应一个人到底意味着什么()
- 我眼中的佛系经纪人
- 《魔法科高中的劣等生》第26卷(Invasion篇)发售