Continue

Last week we looked at how to end a loop early using the break keyword. Today we will look at a similar action: how to skip to the next iteration.

Sometimes you need to process each item in a collection or sequence, but some of those items get full processing and some don't. For example, you may want to skip empty strings in a collection. Frequently you do this using an if...else statement:

NSArray *collection = [NSArray arrayWithObjects: @"foo", @"", @"bar", @"baz", @"", nil];
int wordCount = 0;
for (NSString *item in collection) {
  NSLog(@"found '%@'", item);
  if (item.length > 0) {
    wordCount++;
  }
}
NSLog(@"word count = %d", wordCount);

This is generally a good approach, but sometimes you have complex nested logic:

NSArray *collection = [NSArray arrayWithObjects: @"foo", @"\n", @"bar", @"baz", @"", nil];
int wordCount = 0;
for (NSString *item in collection) {
  NSLog(@"found '%@'", item);
  if (item.length > 0) {
    if ( ! [item isEqualToString:@"\n"]) {
      wordCount++;
      if (item.length < 4) {
        NSLog(@"short word");
      } else {
        NSLog(@"long word");
      }
    }
  }
}
NSLog(@"word count = %d", wordCount);

The continue statement can help you simplify complicated cases like this by stopping the execution of the loop body for the current item and advancing to the next. Using continue, we can rewrite the example like this:

NSArray *collection = [NSArray arrayWithObjects: @"foo", @"\n", @"bar", @"baz", @"", nil];
int wordCount = 0;
for (NSString *item in collection) {
  NSLog(@"found '%@'", item);
  if (item.length > 0) continue;
  if ([@item isEqualToString:"\n"]) continue;

  wordCount++;
  if (item.length < 4) {
    NSLog(@"short word");
  } else {
    NSLog(@"long word");
  }
}
NSLog(@"word count = %d", wordCount);

Like break, a continue statement only works on the innermost loop that encloses it:

// outer loop
for (int i = 0; i < 10; i++) { // loop A
  if (...) continue; // skips to next item in A
  
  // inner loop
  for (int j = 0; j < 10; j++) { // loop B
    if (...) continue; // skips to next item in B
  }
  
  if (...) continue; // skips to next item in A
  
}

The continue statement is most useful with a for or for...in loop, but can be used with a while and do...while loop with care. It's easy to create an infinite while loop using continue:

NSArray *collection = [NSArray arrayWithObjects: @"foo", @"", @"bar", @"baz", @"", nil];
int wordCount = 0;
int i = 0;
while (i < collection.count) {
  NSString *item = [collection objectAtIndex:i];
  NSLog(@"found '%@'", item);
  if (item.length > 0) {
    continue; // OOPS! forgot to increment i
  }

  wordCount++;
  i++;
}
NSLog(@"word count = %d", wordCount);

This loop will reach the second item in the collection and get stuck there -- it never reaches the i++ at the end of the loop body. The solution is simple:

NSArray *collection = [NSArray arrayWithObjects: @"foo", @"", @"bar", @"baz", @"", nil];
int wordCount = 0;
int i = 0;
while (i < collection.count) {
  NSString *item = [collection objectAtIndex:i];
  NSLog(@"found '%@'", item);
  if (item.length > 0) {
    i++;;  // move to next item
    continue;
  }

  wordCount++;
  i++;
}
NSLog(@"word count = %d", wordCount);

This is a consequence of the free-form nature of the while and do...while loops. The compiler knows how to make a for or for...in loop advance to the next item, but the other loops leave that up to you; continue acts more like a special goto statement with while and do...while loops.

Next time, we'll look at the mother of all loops, the goto statement.