Vincent Gable’s Blog

April 19, 2009

Beware rangeOf NSString Operations

Filed under: Bug Bite,iPhone,Objective-C,Programming,Sample Code | , , , ,
― Vincent Gable on April 19, 2009

I have repeatedly had trouble with the rageOfNSString methods, because they return a struct. Going forward I will do more to avoid them, here are some ways I plan to do it.

Sending a message that returns a struct to nil can “return” undefined values. With small structs like NSRange, you are more likely to get {0} on Intel, compared to PowerPC and iPhone/ARM. Unfortunately, this makes nil-messaging bugs hard to detect. In my experience you will miss them when running on the simulator, even if they are 100% reproducible on an actual iPhone.

This category method has helped me avoid using -rangeOfString: dangerously,

@implementation NSString  (HasSubstring)
- (BOOL) hasSubstring:(NSString*)substring;
{
	if(IsEmpty(substring))
		return NO;
	NSRange substringRange = [self rangeOfString:substring];
	return substringRange.location != NSNotFound && substringRange.length > 0;
}
@end

I choose to define [aString hasSubstring:@""] as NO. You might prefer to throw an exception, or differentiate between @"" and nil. But I don’t think a nil string is enough error to throw an exception. And even though technically any string contains the empty string, I generally treat @"" as semantically equivalent to nil.

As I’ve said before,

A few simple guidelines can help you avoid my misfortune:

  • Be especially careful using of any objective-C method that returns a double, struct, or long long
  • Don’t write methods that return a double, struct, orlong long. Return an object instead of a struct; an NSNumber* or float instead of a double or long long. If you must return a dangerous data type, then see if you can avoid it. There really isn’t a good reason to return a struct, except for efficiency. And when micro-optimizations like that matter, it makes more sense to write that procedure in straight-C, which avoids the overhead of Objective-C message-passing, and solves the undefined-return-value problem.
  • But if you absolutely must return a dangerous data type, then return it in a parameter. That way you can give it a default value of your choice, and won’t have undefined values if an object is unexpectedly nil.
    Bad:
    - (struct CStructure) evaluateThings;
    Good:
    - (void) resultOfEvaluatingThings:(struct CStructure*)result;.

It’s not a bad idea to wrap up all the rangeOf methods in functions or categories that play safer with nil.

Thanks to Greg Parker for corrections!

4 Comments »

  1. Not sure I agree with your recommendations about returning ‘dangerous data types’. But in any case, note that on 10.5 ‘double’ is not ‘dangerous’, see:
    http://www.sealiesoftware.com/blog/archive/2008/11/16/objc_explain_objc_msgSend_fpret.html

    Comment by Sean — April 19, 2009 @ 9:55 pm

  2. @Sean,

    To make my example more clear I’ve changed it to return struct not a double.

    But as long as there is a widely-supported platform where X is dangerous, I try to think of X as dangerous everywhere. There are enough 10.4 systems out there today that I generally don’t want to write code that assumes double is safe. I’m just too likely to reuse code in another project that runs on 10.4.

    What part of the recommendation to return dangerous things as parameters don’t you like? I’ll be honest, I’ve never actually done that! It’s just seemed like a reasonable workaround “on paper”. I’d love to hear about problems with it instead of running into them myself one day :-).

    Comment by Vincent Gable — April 20, 2009 @ 9:21 am

  3. Note that struct-return messages to nil return undefined values on Intel as well. It’s just that you’re more likely to coincidentally get {0} for small structs on Intel than you are on other architectures.

    Comment by Greg Parker — April 22, 2009 @ 8:22 pm

  4. Thanks for the corrections! I’ve updated the article, and started re-reading the runtime documentation.

    Comment by Vincent Gable — April 22, 2009 @ 8:52 pm

RSS feed for comments on this post.

Leave a comment

Powered by WordPress