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

在Cocoa中,如果我想循环遍历一个NSMutableArray并删除符合某个条件的多个对象,那么每次删除一个对象时,如果不重新启动循环,最好的方法是什么?

谢谢,

编辑:只是为了澄清 – 我正在寻找最好的方式,例如比我手动更新索引更优雅。 例如在C + +我可以做;

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

为了清晰起见,我喜欢在收集要删除的项目时进行初始循环。 然后我删除它们。 下面是一个使用Objective-C 2.0语法的示例:

 NSMutableArray *discardedItems = [NSMutableArray array]; SomeObjectClass *item; for (item in originalArrayOfItems) { if ([item shouldBeDiscarded]) [discardedItems addObject:item]; } [originalArrayOfItems removeObjectsInArray:discardedItems]; 

那么索引是否正确更新,或其他小簿记细节是没有问题的。

编辑添加:

在其他答案中已经注意到,逆配方应该更快。 即如果你迭代通过数组,并组成一个新的对象数组来保持,而不是丢弃的对象。 这可能是真的(尽pipe如何分配一个新的数组的内存和处理成本,并废弃旧的?),但即使速度更快,它可能不会像一个天真的实现那么大的交易,因为NSArrays不要像“正常”的数组。 他们谈话,但他们走了一个不同的步行。 在这里看到一个好的分析:

http://ridiculousfish.com/blog/archives/2005/12/23/array/

逆配方可能会更快,但我从来不需要关心它是否是因为上述公式一直足以满足我的需求。

对我来说,回家的信息是使用任何最清晰的expression方式。 只在必要时进行优化。 我个人认为上面的表述最清楚,这就是我使用它的原因。 但是,如果反面的说法对你更清楚,就去做吧。

还有一个变化。 所以你得到可读性和良好的性能:

 NSMutableIndexSet *discardedItems = [NSMutableIndexSet indexSet]; SomeObjectClass *item; NSUInteger index = 0; for (item in originalArrayOfItems) { if ([item shouldBeDiscarded]) [discardedItems addIndex:index]; index++; } [originalArrayOfItems removeObjectsAtIndexes:discardedItems]; 

其他一些答案在非常大的数组上会有很差的性能,因为像removeObject:removeObjectsInArray:这样的方法涉及到对接收器进行线性search,这是很浪费的,因为你已经知道对象在哪里了。 此外,对removeObjectAtIndex:任何调用都必须removeObjectAtIndex:将索引中的值复制到数组的末尾。

效率更高的是以下几点:

 NSMutableArray *array = ... NSMutableArray *itemsToKeep = [NSMutableArray arrayWithCapacity:[array count]]; for (id object in array) { if (! shouldRemove(object)) { [itemsToKeep addObject:object]; } } [array setArray:itemsToKeep]; 

因为我们设置了itemsToKeep的容量, itemsToKeep我们不会浪费任何时间在resize的时候复制值。 我们不修改arrays,所以我们可以自由使用快速枚举。 使用setArray:itemsToKeepreplacearray的内容将是高效的。 根据你的代码,你甚至可以用最后一行代替:

 [array release]; array = [itemsToKeep retain]; 

所以甚至不需要复制值,只需交换一个指针即可。

为了大声哭泣! 这是一个非常简单的问题。 你只是向后迭代:

 for (NSInteger i = array.count - 1; i >= 0; i--) { ElementType* element = array[i]; if ([element shouldBeRemoved]) { [array removeObjectAtIndex:i]; } } 

这是一个非常普遍的模式,但只有延斯似乎已经得到了它。

您可以使用NSpredicate从可变数组中删除项目。 这不需要循环。

例如,如果你有一个NSMutableArray的名字,你可以创build一个这样的谓词:

 NSPredicate *caseInsensitiveBNames = [NSPredicate predicateWithFormat:@"SELF beginswith[c] 'b'"]; 

下面的代码会给你一个只包含以b开头的名字的数组。

 [namesArray filterUsingPredicate:caseInsensitiveBNames]; 

如果您在创build所需的谓词时遇到困难,请使用此Apple开发人员链接 。

要么使用循环倒数索引 – 对于(NSInteger i = array.count – 1; i> = 0; –i) – 或者使用要保留的对象进行复制。 特别是,不要使用for(id对象在数组中)循环或NSEnumerator。

我使用4种不同的方法进行了性能testing。 每个testing迭代100000个元素数组中的所有元素,并移除每个第5个项目。 没有优化的结果没有太大的变化。 这些是在iPad 4上完成的:

(1) removeObjectAtIndex:271毫秒

(2) removeObjectsAtIndexes:1010毫秒 (因为构build索引集需要~700毫秒;否则这基本上与为每个项调用removeObjectAtIndex相同)

(3) removeObjects:326 ms

(4)通过testing的对象创build一个新的arrays – 17毫秒

所以,创build一个新arrays是迄今为止最快的。 除了使用removeObjectsAtIndexes:之外,其他方法都是可比较的,因为构build索引集所需的时间会更多。

对于iOS 4+或OS X 10.6+,Apple在NSMutableArray添加了passingTest系列的API,如– indexesOfObjectsPassingTest: 具有这种API的解决scheme将是:

 NSIndexSet *indexesToBeRemoved = [someList indexesOfObjectsPassingTest: ^BOOL(id obj, NSUInteger idx, BOOL *stop) { return [self shouldRemove:obj]; }]; [someList removeObjectsAtIndexes:indexesToBeRemoved]; 

现在,您可以使用反向的基于块的枚举。 一个简单的示例代码:

 NSMutableArray *array = [@[@{@"name": @"a", @"shouldDelete": @(YES)}, @{@"name": @"b", @"shouldDelete": @(NO)}, @{@"name": @"c", @"shouldDelete": @(YES)}, @{@"name": @"d", @"shouldDelete": @(NO)}] mutableCopy]; [array enumerateObjectsWithOptions:NSEnumerationReverse usingBlock:^(id obj, NSUInteger idx, BOOL *stop) { if([obj[@"shouldDelete"] boolValue]) [array removeObjectAtIndex:idx]; }]; 

结果:

 ( { name = b; shouldDelete = 0; }, { name = d; shouldDelete = 0; } ) 

另一个选项只有一行代码:

 [array filterUsingPredicate:[NSPredicate predicateWithFormat:@"shouldDelete == NO"]]; 

以更具说明性的方式,根据匹配要删除的项目的条件,您可以使用:

 [theArray filterUsingPredicate:aPredicate] 

@Nathan应该非常高效

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

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

通过这种方式,您可以枚举要从中删除的数组副本,这两个副本保存相同的对象。 一个NSArray只保存对象指针,所以这是非常好的内存/性能明智的。

将要移除的对象添加到第二个数组中,并在循环之后使用-removeObjectsInArray :.

这应该做到这一点:

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

希望这可以帮助…

为什么不把要移除的对象添加到另一个NSMutableArray。 完成迭代后,可以删除已收集的对象。

如果数组中的所有对象都是唯一的,或者想要在find对象时删除所有对象,则可以快速枚举数组副本并使用[NSMutableArray removeObject:]从原始对象中删除对象。

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

上面的benzado的anwser是你应该做的preformace。 在我的一个应用程序中removeObjectsInArray花了1分钟的运行时间,只是添加到一个新的数组花了0.023秒。

我定义了一个让我使用块进行过滤的类,如下所示:

 @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]; }]; 

更好的实现可能是在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,则删除该对象。

date数组的一个例子是删除过去的所有date:

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

把你想要删除的元素换成第n个元素,第n-1个元素等等如何?

完成后,将数组大小调整为“以前的大小 – 交换次数”

迭代迭代是我多年来的最爱,但是很长一段时间里,我从来没有遇到过“最深”(最高统计量)对象被首先删除的情况。 在指针移动到下一个索引之前的一瞬间,没有任何东西崩溃。

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];