遍历数组的同时删除元素引起崩溃的问题
一、问题
使用 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];
}