28

I want to get the index of the current object when using fast enumeration, i.e.

for (MyClass *entry in savedArray) {
// What is the index of |entry| in |savedArray|?
}
jscs
  • 63,694
  • 13
  • 151
  • 195
Shri
  • 2,129
  • 2
  • 22
  • 32

6 Answers6

64

Look at the API for NSArray and you will see the method

- (void)enumerateObjectsUsingBlock:(void (^)(id obj, NSUInteger idx, BOOL *stop))block

So give that one a try

[savedArray enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {

    //... Do your usual stuff here

    obj  // This is the current object
    idx  // This is the index of the current object
    stop // Set this to true if you want to stop

}];
Paul.s
  • 38,494
  • 5
  • 70
  • 88
  • 10
    to add - the stop flag is used by calling `*stop = YES` (simply `stop = YES` does not work) – Tim Sep 13 '13 at 21:46
9

I suppose the most blunt solution to this would be to simply increment an index manually.

NSUInteger indexInSavedArray = 0;
for (MyClass *entry in savedArray) {
   indexInSavedArray++;
 }

Alternatively, you could just not use fast enumeration.

    for (NSUInteger indexInSavedArray = 0; indexInSavedArray < savedArray.count; indexInSavedArray++) {
       [savedArray objectAtIndex:indexInSavedArray];
     }
Josh Rosen
  • 1,686
  • 1
  • 12
  • 17
  • 5
    Also he can use `[savedArray indexOfObject:entry];`:) – Roman Temchenko Nov 13 '11 at 18:07
  • 6
    Yes, that would work, although it would be very inefficient since it would have to send isEqual to every object leading up to the matching object. E.g., it would take 500 isEqual message sends (and calculations if the object actually does something in real in isEqual) to get the index of the 500th element, and then 501 more message sends to get the next object after that. edit: perhaps you were being sarcastic and I missed a joke :) – Josh Rosen Nov 15 '11 at 03:35
7

This question has already been answered, but I thought I would add that counting iterations is actually the technique mentioned in the iOS Developer Library documentation:

NSArray *array = <#Get an array#>;
NSUInteger index = 0;

for (id element in array) {
    NSLog(@"Element at index %u is: %@", index, element);
    index++;
}

I was sure there would be a fancy trick, but I guess not. :)

GrandAdmiral
  • 1,348
  • 2
  • 24
  • 52
  • 1
    Apple's way is unsafe and IMHO to be avoided - it goes horribly wrong when someone innocently adds a "break" or a "continue" to your code. Sadly, their design for Fast Enumeration just wasn't very good. – Adam Mar 06 '12 at 23:18
  • Do you mean something goes wrong if the "break" or "continue" is placed inside the Fast Enumeration code? Did Apple do something different than, say, C#? Just asking for my own education (and avoidance of future bugs). Thanks! – GrandAdmiral Mar 25 '12 at 22:18
  • 3
    Simple example: the example code is 2 lines long. What if it's 20 lines long, and at line 5, someone adds a "continue"? The continue will cause the "element" to advance - but not the "index". Aha! So you put the index++ at the start, just after the point where it was used. But what if someone then tries to reference it later in the loop body? Now it's too large by 1. ... etc – Adam Mar 25 '12 at 22:22
  • 2
    @Adam although I agree with your point that Fast Enumeration wasn't very well designed, couldn't you just use `NSUInteger index=-1;` and then increment index at the start? – Jimmery Aug 23 '13 at 11:16
  • @jimmery yep, that would would fine. Except it looks wrong - you're creating an index at an incorrect value, and I've seen many cases of copy/paste code, or refactored code, where the importance of that "-1" was lost. Basically: DONT USE FAST ENUMERATION WHEN YOU NEED A COUNTER (it's dangerous and all it saves you is a line of code) – Adam Aug 23 '13 at 12:58
  • NSUInteger is also an unsigned integer, so that line of code would end up being a massive number. – Sam Clewlow Feb 18 '14 at 18:07
3

If you want to access the index or return outside block here is a piece of code that can be useful. (considering the array is an array of NSString).

- (NSInteger) findElemenent:(NSString *)key inArray:(NSArray *)array
{
    __block NSInteger index = -1;
    [array enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
        if ([obj isEqualToString:key]) {
            *stop = YES;
            index = idx;
        }
    }];
    return index;
}
karim
  • 15,408
  • 7
  • 58
  • 96
0

A simple observation: If you initialize the index to -1 and then put the ++index as the first line in the for loop, doesn't that cover all bases (provided someone doesn't slip code in front of the increment)?

  • 1
    "provided someone doesn't slip code in front of the increment" Faith-based coding at its finest – Tim Sep 13 '13 at 21:48
0

I just had a pretty bad bug because I was doing this the way everyone else in here has suggested. That is, "create an index variable and increment it at the end of your loop".

I propose that this should be avoided and instead the following pattern should be followed:

int index = -1;
for (a in b) {
   index++;
   //Do stuff with `a`
}

The reason I recommend this odd pattern, is because if you use the continue; feature of fast enumeration, it will skip the final index++ line of code at the end of your loop, and your index count will be off! For this reason I recommend starting at -1 and incrementing before doing anything else.

As for people who said just use indexOfObject: this won't work with duplicate entries.

Albert Renshaw
  • 17,282
  • 18
  • 107
  • 195