0

I have a for loop.

for (i=0; i <= stringLength; i++) {

unichar currentCharacter = [string characterAtIndex:i];

...}

I understand from the documentation that characterAtIndex: will usually return a value of the type 'unichar' but if our index is out of bounds an NSRangeException is returned.

Being cautious, I'd like to check whether [string characterAtIndex:i] is returning an NSRangeException or not before assigning to return value to currentCharacter.

The first thing I tried was this:

if ([string characterAtIndex:i] != NSRangeException)

...but it doesn't work because, from my understanding, the method usually returns an int and I'm comparing it to NSRangeException, which is a string.

I thought about [string characterAtIndex:i] isKindOfClass:...] but that won't work because [string characterAtIndex:i] is not an NSObject. So, uh, how do I test for the exception?

Should I be testing for the exception? How do I test for the type of the returned value if it's sometimes a primitive and sometimes an NSObject?

pix
  • 5,052
  • 2
  • 23
  • 25
Oliver
  • 403
  • 3
  • 14
  • 1
    Exceptions are something entirely different from ordinary Objective-C variables. While they are nominally objects they are treated differently and are "signalled", not returned on a call. While it is sometimes possible to "handle" an exception in Objective-C, it's iffy at best, and it's far better to avoid them. To avoid a range exception on a characterAtIndex, make sure that your index value is >=0 and strictly less than the string length. – Hot Licks Dec 11 '13 at 02:50

2 Answers2

1

Hi Oliver, I just stumbled upon this question and thought I'd answer it, though I hope you have worked it out since and haven't been stuck on this for 6 months ;)

Firstly, assuming that stringLength == [string length] then you need to check that the index is strictly less than stringLength, not less that or equal, ie:

for (i=0; i < stringLength; i++) {

  unichar currentCharacter = [string characterAtIndex:i];

...}

As an example, a 5 character string has 5 valid indexes; 0, 1, 2, 3 and 4.

That fix should stop you from getting an exception, but here is some more explanation of exceptions (which I wrote before noticing your off-by-one error in the index):

Exceptions aren't like normal return values, they are like a special emergency channel by which a function can inform its caller of an error condition, without having to jam an error condition into its normal return value.

The terminology is that Exceptions aren't "returned" so much as they are "raised" or "thrown" (depending on the language). An exception is then "caught" or "rescued from" (depending on the language).

Exceptions should only be used in exceptional circumstances, and if there is some way of avoiding an exception (by sanitising input) you should do that first. Another reason to avoid exceptions when possible is that in some languages the process of catching exceptions is not terribly efficient, and can also leave memory allocation a little bit up-in-the air.

But in exceptional circumstances, they are useful way of signalling an error to a calling function without having to contrive some way of expressing the error condition in your range of return values.

So, on to the practicalities. The syntax for catching an exception in your case would be:

@try {
  for (i=0; i <= stringLength; i++) {
    unichar currentCharacter = [string characterAtIndex:i];
  }
} 
@catch (NSException *exception) {
  NSLog(@"uh oh..");
  // handle the exception here
}

That will catch any exception thrown, not just NSRangeException. In many languages you could do be more selective with syntax like @catch (NSRangeException *exception), but unfortunately not in Objective-C. This is because NSRangeException is not defined as a subclass of NSException, but it is instead an NSString constant which will appear in the -name value of an NSException. To be more selective in Objective-C you would need to do something like:

@catch (NSException *exception) {
  if ([[exception name] isEqualToString:NSRangeException]) {
    NSLog("got an NSRangeException");
    // handle the exception here
  } else {
    NSLog("got an exception we can't handle so pass it down the chain");
    @throw exception;
  }
}

As you can see, it can get quite clumsy catching exceptions so it is always best to try avoiding them. If this code triggers an NSRangeException, something very exceptional (and probably beyond your control) must be happening:

for (i=0; i < [string length]; i++) {

  unichar currentCharacter = [string characterAtIndex:i];

...}
pix
  • 5,052
  • 2
  • 23
  • 25
0

It's also worth noting you should basically not do this with mutable strings. Make an immutable copy. If you really want to try and do it with immutable strings you should iterate backward from the end. It's also reasonable to assert the length has not changed in the for loop iteration criteria.

uchuugaka
  • 12,679
  • 6
  • 37
  • 55