NeuroAgent

How to Check String Contains Substring in Objective-C

Learn the most efficient ways to check if an NSString contains a substring in Objective-C. Discover rangeOfString:, hasPrefix, case-insensitive matching, and performance optimization techniques.

How do I check if a string contains another string in Objective-C?

I’m trying to determine if an NSString contains a specific substring. I attempted to use a method like:

objective-c
NSString *string = @"hello bla bla";
NSLog(@"%d",[string containsSubstring:@"hello"]);

However, this method doesn’t exist. The closest approach I found is:

objective-c
if ([string rangeOfString:@"hello"] == 0) {
    NSLog(@"sub string doesnt exist");
} 
else {
    NSLog(@"exists");
}

Is this the most efficient way to check for substring containment in Objective-C, or is there a better approach?

NeuroAgent

To check if an NSString contains another string in Objective-C, you should use the rangeOfString: method, which returns an NSRange struct indicating where the substring occurs. The approach you found is correct but has a subtle issue - you should check if the location is not equal to NSNotFound rather than comparing to 0, as rangeOfString: returns a struct with location NSNotFound (which is typically NSUIntegerMax) when the substring isn’t found.


Contents


NSString Substring Methods

NSString provides several methods for checking substring containment, with rangeOfString: being the most versatile:

objective-c
NSString *mainString = @"hello bla bla";
NSString *substring = @"hello";

// Correct way to check if substring exists
NSRange range = [mainString rangeOfString:substring];
if (range.location != NSNotFound) {
    NSLog(@"Substring exists at location: %lu", (unsigned long)range.location);
} else {
    NSLog(@"Substring doesn't exist");
}

The rangeOfString: method returns an NSRange struct with:

  • location: The starting index of the found substring (or NSNotFound if not found)
  • length: The length of the found substring

Key methods for substring checking:

  1. rangeOfString: - Most flexible, finds any occurrence
  2. rangeOfString:options: - More control with search options
  3. rangeOfString:options:range: - Search within a specific range
  4. rangeOfString:options:range:locale: - Locale-aware searching

RangeOfString vs HasPrefix vs HasSuffix

While rangeOfString: is the most versatile, NSString also provides specialized methods:

objective-c
NSString *string = @"hello bla bla";

// Check if string starts with substring
if ([string hasPrefix:@"hello"]) {
    NSLog(@"String starts with 'hello'");
}

// Check if string ends with substring  
if ([string hasSuffix:@"bla"]) {
    NSLog(@"String ends with 'bla'");
}

// General substring search (most flexible)
NSRange range = [string rangeOfString:@"bla"];
if (range.location != NSNotFound) {
    NSLog(@"Found 'bla' at location: %lu", (unsigned long)range.location);
}

When to use each method:

  • Use hasPrefix: when you only need to check the beginning
  • Use hasSuffix: when you only need to check the end
  • Use rangeOfString: for general substring searching

Efficient Comparison Techniques

The method you found is correct, but here are more efficient and readable approaches:

Method 1: Boolean Extension (Recommended)

Create a category for cleaner syntax:

objective-c
// In NSString+Helper.h
@interface NSString (Helper)
- (BOOL)containsString:(NSString *)string;
@end

// In NSString+Helper.m
@implementation NSString (Helper)
- (BOOL)containsString:(NSString *)string {
    return [self rangeOfString:string].location != NSNotFound;
}
@end

Usage:

objective-c
NSString *string = @"hello bla bla";
if ([string containsString:@"hello"]) {
    NSLog(@"Contains 'hello'");
}

Method 2: Using NSStringCompareOptions

For more control over search behavior:

objective-c
NSString *string = @"Hello Bla Bla";
NSRange range = [string rangeOfString:@"hello" 
                           options:NSCaseInsensitiveSearch];
if (range.location != NSNotFound) {
    NSLog(@"Found 'hello' (case insensitive)");
}

Case-Insensitive Matching

For case-insensitive substring searches:

objective-c
NSString *string = @"Hello Bla Bla";

// Case-insensitive search
NSRange range = [string rangeOfString:@"hello" 
                           options:NSCaseInsensitiveSearch];
if (range.location != NSNotFound) {
    NSLog(@"Found case-insensitive match");
}

// Multiple search options can be combined
range = [string rangeOfString:@"HELLO" 
                    options:NSCaseInsensitiveSearch | NSAnchoredSearch];

Common search options:

  • NSCaseInsensitiveSearch - Ignore case differences
  • NSLiteralSearch - Exact character matching
  • NSAnchoredSearch - Search only at string start
  • NSNumericSearch - Compare numeric strings

Performance Considerations

For performance-critical applications:

  1. Short-circuit with hasPrefix: or hasSuffix: when applicable
  2. Use specific ranges when you know where to look:
    objective-c
    NSRange range = [string rangeOfString:@"bla" 
                                 options:0 
                                   range:NSMakeRange(0, 10)]; // Search only first 10 chars
    
  3. Avoid repeated searches - store results if checking multiple times
  4. Consider character-by-character comparison for very short strings

Benchmark comparison:

objective-c
// Fastest for prefix checking
if ([string hasPrefix:@"prefix"]) { ... }

// Flexible but slightly slower
if ([string rangeOfString:@"substring"].location != NSNotFound) { ... }

// Extension method (recommended for readability)
if ([string containsString:@"substring"]) { ... }

Modern Alternatives

With modern Objective-C and Swift interoperability:

Swift Integration

objective-c
// Swift bridging allows using Swift's contains method
NSString *string = @"hello bla bla";
BOOL contains = [string contains:@"hello"]; // Swift-style

Using Regular Expressions

For complex pattern matching:

objective-c
NSString *string = @"hello bla bla";
NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:@"bla" 
                                                                      options:0 
                                                                        error:nil];
if ([regex firstMatchInString:string options:0 range:NSMakeRange(0, string.length)]) {
    NSLog(@"Found pattern match");
}

Complete Example

Here’s a comprehensive example showing different approaches:

objective-c
#import <Foundation/Foundation.h>

// Category for containsString method
@interface NSString (SubstringHelper)
- (BOOL)containsString:(NSString *)string;
- (BOOL)containsString:(NSString *)string options:(NSStringCompareOptions)options;
@end

@implementation NSString (SubstringHelper)
- (BOOL)containsString:(NSString *)string {
    return [self rangeOfString:string].location != NSNotFound;
}

- (BOOL)containsString:(NSString *)string options:(NSStringCompareOptions)options {
    return [self rangeOfString:string options:options].location != NSNotFound;
}
@end

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        NSString *mainString = @"Hello World Programming";
        NSString *substring = @"world";
        
        // Method 1: Using rangeOfString directly
        NSRange range = [mainString rangeOfString:substring 
                                        options:NSCaseInsensitiveSearch];
        if (range.location != NSNotFound) {
            NSLog(@"Found '%@' at location: %lu", substring, (unsigned long)range.location);
        }
        
        // Method 2: Using hasPrefix/hasSuffix
        if ([mainString hasPrefix:@"Hello"]) {
            NSLog(@"String starts with 'Hello'");
        }
        
        // Method 3: Using category method
        if ([mainString containsString:substring options:NSCaseInsensitiveSearch]) {
            NSLog(@"Contains '%@' (case insensitive)", substring);
        }
        
        // Method 4: Getting substring length
        if ([mainString containsString:@"World"]) {
            NSLog(@"Substring 'World' length: %lu", 
                  (unsigned long)[mainString rangeOfString:@"World"].length);
        }
    }
    return 0;
}

Output:

Found 'world' at location: 6
String starts with 'Hello'
Contains 'world' (case insensitive)
Substring 'World' length: 5

Conclusion

The most efficient and readable approach to check if an NSString contains a substring is to use the rangeOfString: method and verify that the location is not NSNotFound. While the method you found is conceptually correct, comparing to 0 instead of NSNotFound is a subtle bug.

Key recommendations:

  1. Use [string rangeOfString:substring].location != NSNotFound for the most reliable check
  2. Create a category method like containsString: for cleaner, more readable code
  3. Use specialized methods like hasPrefix: and hasSuffix: when appropriate
  4. Consider search options like NSCaseInsensitiveSearch for case-insensitive matching
  5. For optimal performance, use ranges and short-circuit methods when possible

The rangeOfString: approach is both efficient and flexible, making it the standard method for substring containment checks in Objective-C.


Sources

  1. Apple NSString Documentation - rangeOfString:
  2. NSStringCompareOptions Reference
  3. NSRange and NSNotFound Documentation
  4. Objective-C String Programming Guide