objective-c 迭代时从 NSMutableArray 中删除的最佳方法?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/111866/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Best way to remove from NSMutableArray while iterating?
提问by Andrew Grant
In Cocoa, if I want to loop through an NSMutableArray and remove multiple objects that fit a certain criteria, what's the best way to do this without restarting the loop each time I remove an object?
在 Cocoa 中,如果我想遍历 NSMutableArray 并删除符合特定条件的多个对象,那么在每次删除对象时无需重新启动循环的最佳方法是什么?
Thanks,
谢谢,
Edit: Just to clarify - I was looking for the best way, e.g. something more elegant than manually updating the index I'm at. For example in C++ I can do;
编辑:只是为了澄清 - 我正在寻找最好的方法,例如比手动更新我所在的索引更优雅的东西。例如在 C++ 中我可以做到;
iterator it = someList.begin();
while (it != someList.end())
{
if (shouldRemove(it))
it = someList.erase(it);
}
回答by Christopher Ashworth
For clarity I like to make an initial loop where I collect the items to delete. Then I delete them. Here's a sample using Objective-C 2.0 syntax:
为清楚起见,我喜欢做一个初始循环,在那里我收集要删除的项目。然后我删除它们。下面是一个使用 Objective-C 2.0 语法的示例:
NSMutableArray *discardedItems = [NSMutableArray array];
for (SomeObjectClass *item in originalArrayOfItems) {
if ([item shouldBeDiscarded])
[discardedItems addObject:item];
}
[originalArrayOfItems removeObjectsInArray:discardedItems];
Then there is no question about whether indices are being updated correctly, or other little bookkeeping details.
那么就没有关于索引是否正确更新的问题,或者其他小的簿记细节。
Edited to add:
编辑添加:
It's been noted in other answers that the inverse formulation should be faster. i.e. If you iterate through the array and compose a new array of objects to keep, instead of objects to discard. That may be true (although what about the memory and processing cost of allocating a new array, and discarding the old one?) but even if it's faster it may not be as big a deal as it would be for a naive implementation, because NSArrays do not behave like "normal" arrays. They talk the talk but they walk a different walk. See a good analysis here:
在其他答案中已经指出,逆公式应该更快。即如果您遍历数组并组成一个新的对象数组来保留,而不是要丢弃的对象。这可能是正确的(尽管分配新数组和丢弃旧数组的内存和处理成本如何?)但即使它更快,它也可能不像天真的实现那么大,因为 NSArrays不要表现得像“普通”数组。他们谈论话题,但他们走的路不同。在这里看到一个很好的分析:
The inverse formulation may be faster, but I've never needed to care whether it is, because the above formulation has always been fast enough for my needs.
逆公式可能会更快,但我从来不需要关心它是否更快,因为上述公式对于我的需求总是足够快。
For me the take-home message is to use whatever formulation is clearest to you. Optimize only if necessary. I personally find the above formulation clearest, which is why I use it. But if the inverse formulation is clearer to you, go for it.
对我来说,带回家的信息是使用您最清楚的任何公式。仅在必要时进行优化。我个人认为上面的公式最清楚,这就是我使用它的原因。但如果逆公式对你来说更清楚,那就去做吧。
回答by Corey Floyd
One more variation. So you get readability and good performace:
还有一种变体。因此,您可以获得可读性和良好的性能:
NSMutableIndexSet *discardedItems = [NSMutableIndexSet indexSet];
SomeObjectClass *item;
NSUInteger index = 0;
for (item in originalArrayOfItems) {
if ([item shouldBeDiscarded])
[discardedItems addIndex:index];
index++;
}
[originalArrayOfItems removeObjectsAtIndexes:discardedItems];
回答by Hot Licks
This is a very simple problem. You just iterate backwards:
这是一个非常简单的问题。您只需向后迭代:
for (NSInteger i = array.count - 1; i >= 0; i--) {
ElementType* element = array[i];
if ([element shouldBeRemoved]) {
[array removeObjectAtIndex:i];
}
}
This is a very common pattern.
这是一个很常见的模式。
回答by benzado
Some of the other answers would have poor performance on very large arrays, because methods like removeObject:and removeObjectsInArray:involve doing a linear search of the receiver, which is a waste because you already know where the object is. Also, any call to removeObjectAtIndex:will have to copy values from the index to the end of the array up by one slot at a time.
其他一些答案在非常大的数组上的性能会很差,因为方法喜欢removeObject:并且removeObjectsInArray:涉及对接收器进行线性搜索,这是一种浪费,因为您已经知道对象在哪里。此外,任何调用removeObjectAtIndex:都必须将值从索引复制到数组的末尾,一次一个插槽。
More efficient would be the following:
更有效的将是以下内容:
NSMutableArray *array = ...
NSMutableArray *itemsToKeep = [NSMutableArray arrayWithCapacity:[array count]];
for (id object in array) {
if (! shouldRemove(object)) {
[itemsToKeep addObject:object];
}
}
[array setArray:itemsToKeep];
Because we set the capacity of itemsToKeep, we don't waste any time copying values during a resize. We don't modify the array in place, so we are free to use Fast Enumeration. Using setArray:to replace the contents of arraywith itemsToKeepwill be efficient. Depending on your code, you could even replace the last line with:
因为我们设置了 的容量itemsToKeep,所以我们不会在调整大小期间浪费任何时间复制值。我们不会就地修改数组,因此我们可以自由使用快速枚举。使用setArray:来替换arraywith的内容itemsToKeep会很有效。根据您的代码,您甚至可以将最后一行替换为:
[array release];
array = [itemsToKeep retain];
So there isn't even a need to copy values, only swap a pointer.
所以甚至不需要复制值,只需要交换一个指针。
回答by benzado
You can use NSpredicate to remove items from your mutable array. This requires no for loops.
您可以使用 NSpredicate 从可变数组中删除项目。这不需要 for 循环。
For example if you have an NSMutableArray of names, you can create a predicate like this one:
例如,如果您有一个 NSMutableArray 名称,您可以创建一个这样的谓词:
NSPredicate *caseInsensitiveBNames =
[NSPredicate predicateWithFormat:@"SELF beginswith[c] 'b'"];
The following line will leave you with an array that contains only names starting with b.
以下行将为您留下一个仅包含以 b 开头的名称的数组。
[namesArray filterUsingPredicate:caseInsensitiveBNames];
If you have trouble creating the predicates you need, use this apple developer link.
如果您在创建所需的谓词时遇到问题,请使用此苹果开发人员链接。
回答by user1032657
I did a performance test using 4 different methods. Each test iterated through all elements in a 100,000 element array, and removed every 5th item. The results did not vary much with/ without optimization. These were done on an iPad 4:
我使用 4 种不同的方法进行了性能测试。每个测试都遍历 100,000 个元素数组中的所有元素,并每 5 个元素删除一次。有/没有优化,结果变化不大。这些是在 iPad 4 上完成的:
(1) removeObjectAtIndex:-- 271 ms
(1) removeObjectAtIndex:-- 271 毫秒
(2) removeObjectsAtIndexes:-- 1010 ms(because building the index set takes ~700 ms; otherwise this is basically the same as calling removeObjectAtIndex: for each item)
(2) removeObjectsAtIndexes:-- 1010 ms(因为建立索引集需要~700 ms;否则这与为每个项目调用removeObjectAtIndex:基本相同)
(3) removeObjects:-- 326 ms
(3) removeObjects:-- 326 毫秒
(4) make a new array with objects passing the test -- 17 ms
(4) 用通过测试的对象创建一个新数组——17 毫秒
So, creating a new array is by far the fastest. The other methods are all comparable, except that using removeObjectsAtIndexes: will be worse with more items to remove, because of the time needed to build the index set.
因此,创建新数组是迄今为止最快的。其他方法都是可比的,除了使用 removeObjectsAtIndexes: 删除更多项目时会更糟,因为构建索引集需要时间。
回答by Jens Ayton
Either use loop counting down over indices:
要么使用循环对索引进行倒计时:
for (NSInteger i = array.count - 1; i >= 0; --i) {
or make a copy with the objects you want to keep.
或使用您要保留的对象制作副本。
In particular, do not use a for (id object in array)loop or NSEnumerator.
特别是,不要使用for (id object in array)循环或NSEnumerator.
回答by zavié
For iOS 4+ or OS X 10.6+, Apple added passingTestseries of APIs in NSMutableArray, like – indexesOfObjectsPassingTest:. A solution with such API would be:
对于iOS 4以上版本或OS X 10.6以上,苹果增加了passingTest在原料药系列NSMutableArray,喜欢– indexesOfObjectsPassingTest:。具有此类 API 的解决方案是:
NSIndexSet *indexesToBeRemoved = [someList indexesOfObjectsPassingTest:
^BOOL(id obj, NSUInteger idx, BOOL *stop) {
return [self shouldRemove:obj];
}];
[someList removeObjectsAtIndexes:indexesToBeRemoved];
回答by vikingosegundo
Nowadays you can use reversed block-based enumeration. A simple example code:
现在你可以使用反向的基于块的枚举。一个简单的示例代码:
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];
}];
Result:
结果:
(
{
name = b;
shouldDelete = 0;
},
{
name = d;
shouldDelete = 0;
}
)
another option with just one line of code:
只需一行代码的另一种选择:
[array filterUsingPredicate:[NSPredicate predicateWithFormat:@"shouldDelete == NO"]];
回答by vikingosegundo
In a more declarative way, depending on the criteria matching the items to remove you could use:
以更具声明性的方式,根据与要删除的项目匹配的条件,您可以使用:
[theArray filterUsingPredicate:aPredicate]
@Nathan should be very efficient
@Nathan 应该非常高效

