欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

遍历数组的同时删除元素引起崩溃的问题以及解决方案

程序员文章站 2024-02-09 18:02:04
...

遍历数组的同时删除元素引起崩溃的问题

一、问题

使用 for in遍历可变数组的同时删除元素会造成崩溃

崩溃日志为:

2018-03-10 19:40:43.970096+0800 ArrayRemoveObj[6500:611281] *** Terminating app due to uncaught exception 'NSGenericException', reason: '*** Collection <__NSArrayM: 0x60400005a9a0> was mutated while being enumerated.'
*** First throw call stack:
(
    0   CoreFoundation                      0x0000000103cc012b __exceptionPreprocess + 171
    1   libobjc.A.dylib                     0x0000000103354f41 objc_exception_throw + 48
    2   CoreFoundation                      0x0000000103d33b0c __NSFastEnumerationMutationHandler + 124
    3   ArrayRemoveObj                      0x0000000102a476a2 main + 834
    4   libdyld.dylib                       0x00000001076e8d81 start + 1
)
libc++abi.dylib: terminating with uncaught exception of type NSException

所以 遍历可变数组时,最好不要做删除数组中元素的操作,因为删除操作可能会引起数组容量的变化,导致数组越界等问题。

二、分析

结合网上资料及实际代码对for in, enumerateObjectsUsingBlock:,for进行实际的分析。

声明如下数组:

NSMutableArray *array = [NSMutableArray arrayWithArray:@[@"aaa",@"bbb",@"ccc",@"ddd",@"eee"]];

使用 for in 必崩:

for (NSString *obj in array) {
    if ([obj isEqualToString:@"ddd"]) {
        [array removeObject:obj];
    }
}

使用 enumerateObjectsUsingBlock:未崩溃

[array enumerateObjectsUsingBlock:^(id  _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
    if ([obj isEqualToString:@"ddd"]) {
        [array removeObject:obj];
    }
}];

使用 for未崩溃

for (int i = 0; i < array.count; i++) {
    NSString *obj = array[i];
    if ([obj isEqualToString:@"ddd"]) {
        [array removeObject:obj];
    }
}

记得会造成崩溃,有可能会造成数组越界。据说可能是苹果对此进行了保护,不推荐此法。

三、结论

从上面来看只for in会造成崩溃,但是结合以往经验来看最稳妥的方案,是对数组逆序遍历,然后再删除元素就没有问题了。

使用for in的逆序遍历删除元素:

for (NSString *obj in array.reverseObjectEnumerator) {
    if ([obj isEqualToString:@"ddd"]) {
        [array removeObject:obj];
    }
}

使用enumerateObjectsWithOptions:逆序遍历删除元素:

[array enumerateObjectsWithOptions:NSEnumerationReverse usingBlock:^(id  _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
    if ([obj isEqualToString:@"ddd"]) {
        [array removeObject:obj];
    }
}];

使用for逆序遍历删除元素:

for (int i = array.count - 1; i >= 0; i--) {
    NSString *obj = array[i];
    if ([obj isEqualToString:@"ddd"]) {
        [array removeObject:obj];
    }
}

最后提供一种容易理解的方法:把需要删除的元素放入新的数组中,遍历完成后,统一删除。这种在大量元素操作的时候,效率低下,推荐使用逆序遍历方式。

NSMutableArray *tempArray = nil;
for (NSString *obj in array) {
    if ([obj isEqualToString:@"ddd"]) {
        if (tempArray == nil) {
            tempArray = [NSMutableArray array];
        }
        [tempArray addObject:obj];
    }
}
if (tempArray && tempArray.count>0) {
    [array removeObjectsInArray:tempArray];
}