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:^(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;
    }
}];

  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循环同时读取和操作同一个数组会出现崩溃呢?

因为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);
}

你可能感兴趣的:(iOS中注意使用for..in同时读取和修改数组导致crash)