在Cocoa中,如果我想循环遍历NSMutableArray并删除符合特定条件的多个对象,那么每次删除对象时如何在不重新启动循环的情况下执行此操作的最佳方法是什么?
谢谢,
编辑:只是为了澄清 - 我正在寻找最好的方式,例如比手动更新索引更优雅的东西.例如在C++中,我可以做;
iterator it = someList.begin(); while (it != someList.end()) { if (shouldRemove(it)) it = someList.erase(it); }
Christopher .. 386
为清楚起见,我喜欢做一个初始循环,我收集要删除的项目.然后我删除它们.这是使用Objective-C 2.0语法的示例:
NSMutableArray *discardedItems = [NSMutableArray array]; for (SomeObjectClass *item in originalArrayOfItems) { if ([item shouldBeDiscarded]) [discardedItems addObject:item]; } [originalArrayOfItems removeObjectsInArray:discardedItems];
然后毫无疑问,索引是否正确更新,或其他小额簿记细节.
编辑添加:
在其他答案中已经注意到反向公式应该更快.即如果遍历数组并组成一个新的对象数组来保留,而不是丢弃的对象.这可能是真的(虽然分配一个新阵列的内存和处理成本如何,并丢弃旧阵列?)但即使它更快,它也可能不像天真的实现那样大,因为NSArrays不要表现得像"普通"数组.他们谈论谈话,但他们走了不同的路.在这里看一个很好的分析:
反向配方可能更快,但我从来不需要关心它是否,因为上述配方总是足够快,以满足我的需要.
对我来说,带回家的信息是使用对你来说最清楚的任何表述.仅在必要时进行优化.我个人觉得上面的配方最清楚,这就是我使用它的原因.但如果反向公式对你来说更清楚,那就去吧.
为清楚起见,我喜欢做一个初始循环,我收集要删除的项目.然后我删除它们.这是使用Objective-C 2.0语法的示例:
NSMutableArray *discardedItems = [NSMutableArray array]; for (SomeObjectClass *item in originalArrayOfItems) { if ([item shouldBeDiscarded]) [discardedItems addObject:item]; } [originalArrayOfItems removeObjectsInArray:discardedItems];
然后毫无疑问,索引是否正确更新,或其他小额簿记细节.
编辑添加:
在其他答案中已经注意到反向公式应该更快.即如果遍历数组并组成一个新的对象数组来保留,而不是丢弃的对象.这可能是真的(虽然分配一个新阵列的内存和处理成本如何,并丢弃旧阵列?)但即使它更快,它也可能不像天真的实现那样大,因为NSArrays不要表现得像"普通"数组.他们谈论谈话,但他们走了不同的路.在这里看一个很好的分析:
反向配方可能更快,但我从来不需要关心它是否,因为上述配方总是足够快,以满足我的需要.
对我来说,带回家的信息是使用对你来说最清楚的任何表述.仅在必要时进行优化.我个人觉得上面的配方最清楚,这就是我使用它的原因.但如果反向公式对你来说更清楚,那就去吧.
还有一个变种.因此,您可以获得可读性和良好的性能:
NSMutableIndexSet *discardedItems = [NSMutableIndexSet indexSet]; SomeObjectClass *item; NSUInteger index = 0; for (item in originalArrayOfItems) { if ([item shouldBeDiscarded]) [discardedItems addIndex:index]; index++; } [originalArrayOfItems removeObjectsAtIndexes:discardedItems];
这是一个非常简单的问题.你只是向后迭代:
for (NSInteger i = array.count - 1; i >= 0; i--) { ElementType* element = array[i]; if ([element shouldBeRemoved]) { [array removeObjectAtIndex:i]; } }
这是一种非常常见的模式.
其他一些答案在非常大的阵列上表现不佳,因为方法喜欢removeObject:
并且removeObjectsInArray:
涉及对接收器进行线性搜索,这是一种浪费,因为你已经知道了对象的位置.此外,任何调用removeObjectAtIndex:
都必须一次将一个插槽中的值从索引复制到数组的末尾.
效率更高的是:
NSMutableArray *array = ... NSMutableArray *itemsToKeep = [NSMutableArray arrayWithCapacity:[array count]]; for (id object in array) { if (! shouldRemove(object)) { [itemsToKeep addObject:object]; } } [array setArray:itemsToKeep];
因为我们设置了容量itemsToKeep
,所以在调整大小期间我们不会浪费任何时间复制值.我们不会修改数组,因此我们可以自由使用快速枚举.使用setArray:
替换的内容array
与itemsToKeep
将是有效的.根据您的代码,您甚至可以用以下内容替换最后一行:
[array release]; array = [itemsToKeep retain];
所以甚至不需要复制值,只需交换指针.
您可以使用NSpredicate从可变数组中删除项目.这不需要循环.
例如,如果你有一个名称的NSMutableArray,你可以创建一个像这样的谓词:
NSPredicate *caseInsensitiveBNames = [NSPredicate predicateWithFormat:@"SELF beginswith[c] 'b'"];
以下行将为您提供一个仅包含以b开头的名称的数组.
[namesArray filterUsingPredicate:caseInsensitiveBNames];
如果您在创建所需的谓词时遇到问题,请使用此apple开发人员链接.
我使用4种不同的方法进行了性能测试.每个测试迭代100,000个元素数组中的所有元素,并删除每第5个项目.无论是否优化,结果都没有太大变化.这些是在iPad 4上完成的:
(1)removeObjectAtIndex:
- 271毫秒
(2)removeObjectsAtIndexes:
- 1010 ms(因为构建索引集大约需要700毫秒;否则这与调用removeObjectAtIndex:每个项目基本相同)
(3)removeObjects:
- 326毫秒
(4)使用通过测试的对象创建一个新数组 - 17 ms
因此,创建一个新阵列是迄今为止最快的.其他方法都是可比较的,除了使用removeObjectsAtIndexes:由于构建索引集所需的时间更多,删除的项目会更多.
使用循环倒计数索引:
for (NSInteger i = array.count - 1; i >= 0; --i) {
或者使用您想要保留的对象制作副本.
特别是,不要使用for (id object in array)
循环或NSEnumerator
.
对于iOS 4+或OS X 10.6+,Apple添加了passingTest
一系列API NSMutableArray
,例如– indexesOfObjectsPassingTest:
.这种API的解决方案是:
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--; } }
希望这可以帮助...