在迭代时从NSMutableArray中删除元素的最佳方法是什么?

199
在Cocoa中,如果我想循环遍历NSMutableArray并删除符合特定条件的多个对象,那么在每次删除对象时不重启循环,最好的方法是什么?
谢谢。编辑:只是为了澄清-我正在寻找最好的方法,例如比手动更新我的索引更优雅的方法。例如,在C++中我可以这样做;
iterator it = someList.begin();

while (it != someList.end())
{
    if (shouldRemove(it))   
        it = someList.erase(it);
}

9
从后往前循环。 - Hot Licks
没有人回答“为什么”的问题。 - onmyway133
1
@HotLicks 是我所有时间里最喜欢的,也是编程中最被低估的解决方案之一 :D - Julian F. Weinert
20个回答

6

以下是简单干净的方法。我喜欢在快速枚举调用中复制我的数组:

for (LineItem *item in [NSArray arrayWithArray:self.lineItems]) 
{
    if ([item.toBeRemoved boolValue] == YES) 
    {
        [self.lineItems removeObject:item];
    }
}

通过枚举被删除的数组的副本,您可以同时保留相同的对象。NSArray仅保存对象指针,因此从内存/性能角度来看,这是完全可行的。


2
甚至更简单的是 - for (LineItem *item in self.lineItems.copy) - Alexandre G

5
将您想要移除的对象添加到第二个数组中,在循环结束后使用-removeObjectsInArray:方法。

5
这应该可以做到:
    NSMutableArray* myArray = ....;

    int i;
    for(i=0; i<[myArray count]; i++) {
        id element = [myArray objectAtIndex:i];
        if(element == ...) {
            [myArray removeObjectAtIndex:i];
            i--;
        }
    }

希望这能有所帮助...


虽然有些不正统,但我发现反向迭代并在进行迭代时删除元素是一种简洁而简单的解决方案。通常也是最快的方法之一。 - rpetrich
这个有什么问题吗?它干净、快速、易读,而且运行得非常好。对我来说,它看起来是最佳答案。为什么它得到了负分?我在这里漏掉了什么吗? - Steph Thirion
1
@Steph:问题中提到“比手动更新索引更优雅的解决方案”。 - Steve Madsen
1
哦,我错过了“绝对不想手动更新索引”的部分。谢谢史蒂夫。 在我看来,这个解决方案比选择的那个更优雅(不需要临时数组),所以反对它的负票感觉不公平。 - Steph Thirion
@Steve:如果你查看了编辑记录,那部分是在我提交答案之后添加的……如果不是的话,我会回复“反向迭代是最优雅的解决方案”:)。祝你有美好的一天! - Pokot0
这也得到了我的支持。我认为使用一种效率更低的方法来避免这种方法的“复杂性”是不合理的。 - elsurudo

1

benzado的回答是你应该为性能而做的。在我的一个应用程序中,removeObjectsInArray需要1分钟的运行时间,而仅仅添加到一个新数组只需要0.023秒。


1
为什么不将要移除的对象添加到另一个NSMutableArray中呢?当你迭代完成后,你可以删除已收集的对象。

1

你可以考虑将要删除的元素与第'n'个元素、第'n-1'个元素等进行交换,然后再进行删除操作。

完成后,你可以将数组大小调整为“之前的大小 - 交换次数”。


1
如果您的数组中所有对象都是唯一的,或者当找到一个对象时要删除所有出现的对象,您可以快速枚举数组副本,并使用[NSMutableArray removeObject:]从原始数组中删除该对象。
NSMutableArray *myArray;
NSArray *myArrayCopy = [NSArray arrayWithArray:myArray];

for (NSObject *anObject in myArrayCopy) {
    if (shouldRemove(anObject)) {
        [myArray removeObject:anObject];
    }
}

如果在执行+arrayWithArray时原始的myArray被更新了会发生什么? - bioffe
1
@bioffe:那么你的代码中有一个错误。NSMutableArray 不是线程安全的,你应该通过锁来控制访问。请参阅这个答案 - dreamlax

1
我定义了一个类别,让我可以使用代码块进行过滤,就像这样:

@implementation NSMutableArray (Filtering)

- (void)filterUsingTest:(BOOL (^)(id obj, NSUInteger idx))predicate {
    NSMutableIndexSet *indexesFailingTest = [[NSMutableIndexSet alloc] init];

    NSUInteger index = 0;
    for (id object in self) {
        if (!predicate(object, index)) {
            [indexesFailingTest addIndex:index];
        }
        ++index;
    }
    [self removeObjectsAtIndexes:indexesFailingTest];

    [indexesFailingTest release];
}

@end

然后可以像这样使用:

[myMutableArray filterUsingTest:^BOOL(id obj, NSUInteger idx) {
    return [self doIWantToKeepThisObject:obj atIndex:idx];
}];

1
更好的实现方法是在NSMutableArray上使用下面的类别方法。
@implementation NSMutableArray(BMCommons)

- (void)removeObjectsWithPredicate:(BOOL (^)(id obj))predicate {
    if (predicate != nil) {
        NSMutableArray *newArray = [[NSMutableArray alloc] initWithCapacity:self.count];
        for (id obj in self) {
            BOOL shouldRemove = predicate(obj);
            if (!shouldRemove) {
                [newArray addObject:obj];
            }
        }
        [self setArray:newArray];
    }
}

@end

谓语块可以被实现用于对数组中的每个对象进行处理。如果谓语返回true,则该对象将被移除。

一个日期数组的示例,用于移除所有过去的日期:

NSMutableArray *dates = ...;
[dates removeObjectsWithPredicate:^BOOL(id obj) {
    NSDate *date = (NSDate *)obj;
    return [date timeIntervalSinceNow] < 0;
}];

0

反向迭代是我多年来最喜欢的方法,但很长一段时间我从未遇到过首先删除“最深”(计数最高)对象的情况。在指针移动到下一个索引之前的瞬间,没有任何东西并且它会崩溃。

Benzado的方法是我现在所做的最接近的,但我从未意识到每次删除后都会重新排列堆栈。

在Xcode 6下这可以工作。

NSMutableArray *itemsToKeep = [NSMutableArray arrayWithCapacity:[array count]];

    for (id object in array)
    {
        if ( [object isNotEqualTo:@"whatever"]) {
           [itemsToKeep addObject:object ];
        }
    }
    array = nil;
    array = [[NSMutableArray alloc]initWithArray:itemsToKeep];

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接