views:

182

answers:

5

I've heard that it is a bad idea to do something like this. But I am sure there is some rule of thumb which can help to get that right.

When I iterate over an NSMutableDictionary or NSMutableArray often I need to get rid of entries. Typical case: You iterate over it, and compare the entry against something. Sometimes the result is "don't need anymore" and you have to remove it. But doing so affects the index of all the rows, doesn't it?

So how could I safely iterate over it without accidently exceeding bounds or jumping over an element that hasn't been checked?

+6  A: 

You do so by creating a temporary array or dictionary. Either

  • (Dictionary) Add the keys you want to delete later to the temporary array or
  • (Array) Add the indices you want to remove later to an NSIndexSet or
  • Add the objects you want to keep to the temporary array or dictionary

Then either iterate over the temporary TODELETE-list, deleting from the main list (there might be utility methods for this), or replace the existing dictonary/array with your "temporary" one.

Williham Totland
+3  A: 

If you remove an object from a mutable array while you're enumerating the same array, it will crash the app. Keep track of the objects you want to remove (you can keep their indexes in an NSIndexSet) and use the method -(void)removeObjectsAtIndexes:(NSIndexSet *)indexes once you are outside the enumeration loop. Alternatively, keep an NSArray of the objects you're enumerating and use -(void)removeObjectsInArray:(NSArray *)otherArray to delete them once you finish the loop.

Check the NSMutableArray docs for explanations of the methods.

http://developer.apple.com/mac/library/documentation/Cocoa/Reference/Foundation/Classes/NSMutableArray_Class/Reference/Reference.html

nevan
+1  A: 

here's a clever way of creating the copy, iterating, and deleting:

for (id obj in [[myArray copy] autorelease]) {
    BOOL condition = ...
    if (condition) {
        [myArray removeObject:obj];
    }
}

If it's important that you iterate in reverse for some reason:

for (id obj in [[[myArray copy] autorelease] reverseObjectEnumerator]) {
    BOOL condition = ...
    if (condition) {
        [myArray removeObject:obj];
    }
}
Todd Ditchendorf
A: 

Or you can always resort to using explicit index in the loop and then manipulate it while removing objects. So it would look something like this

for(unsigned int i = 0; i < [myArray count]; i++) {
      id obj = [myArray objectAtIndex:i];
      if (condition) {
           [myArray removeObject: obj];
           i--;
      }
}
maciejs
A: 

Given that your array does not contain any [NSNull null] elements (which it most likely won't anyway) you could simply replace all items you need to have removed with [NSNull null] during enumeration and once done enumerating simply call [array removeObjectIdenticalTo:[NSNull null]] ([NSNull null] is a singleton and framework hack to allow insertion of nil into arrays).

This can be handy when memory is an issue and hence array copies not an option or when the array simply cannot be replaced by a copy.

Regexident