美文网首页iOS开发
iOS中注意使用for..in同时读取和修改数组导致crash

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

作者: Jay_小咖 | 来源:发表于2019-01-25 23:54 被阅读0次

    问题

    在工作中遇到数组闪退的问题:
    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

        本文链接:https://www.haomeiwen.com/subject/ladbjqtx.html