macdevcenter.com
oreilly.comSafari Books Online.Conferences.

advertisement

AddThis Social Bookmark Button

Strings in Cocoa: Part I
Pages: 1, 2, 3

Comparing strings

I imagine it won't be long in your programming before you want to compare some strings for equality. In C you learned that you can do so by using the function strcmp(string1, string2):

Editor's Note -- The example below was updated and corrected on 7/05/01. Thanks to our readers for helping us present as accurate information as possible.
char string1[] = "Yo";
char string2[] = "Yo";
if ( strcmp(string1, string2) == 0) {
    // do the following code
}

And the conditional would evaluate to "true", executing the code within the braces of the if statement. In Cocoa, the situation is similar. Remember, whenever we declare a string



NSString *aString;

the variable aString does not actually contain the string object -- it is a pointer to some string object in memory. Another name we had for this type of variable was an object identifier, because it identifies an object in memory rather than hold the object itself. This technical detail has some very important implications, as a pointer is an address to a location in memory. Consider the following situation where we try to compare two string objects using ==:

NSString *string1 = @"A String";
NSString *string2 = @"A String";

Here we have statically created two NSString objects, and they are two separate objects, despite having been endowed with the same value. Now, if we used the C equality operator on them,

BOOL result = string1 == string2;

the equality statement would evaluate to "no" (Objective-C's "false"). That's right, "no". Yes, they look equal, but this line of code did not compare the strings -- it compared the values of their memory addresses. Since they are not the same object, they exist in unique memory locations, and consequently string1 is a different address than string2. This explains the falsity of the statement.

Now, if we had done the following,

NSString *string1 = @"A String.";
NSString *string2 = string1;
BOOL result = string1 == string2;

the equality operator would indeed return "yes", because both string1 and string2 point to the same object in memory -- they have the same address. The line NSString *string2 = string1; accomplished this by taking the address of the object that string1 points to and assigned it to string2 as well. So the addresses are now equal, as was revealed in the equality.

Now, let me make the point clear that if the data type of a variable is int, double, char, or float, the equality operator will work as expected, because these variable data types are not pointers, they actually contain the value of the data. Only object variables (and all pointer variables) fall prey to this phenomenon.

So, I hope you're convinced now that the ways of old just don't work with object-oriented programming. I want to now show you how we do comparisons and make equality judgments.

Whenever you want to check the equality of objects, you must invoke special comparison methods in the respective classes. In NSString, the most straightforward of these is the method - isEqualToString:, whose argument is an NSString object, and returns a boolean value indicating whether the receiver string is equivalent to the argument string. Now, we can truly test the equivalency of our strings:

NSString *string1 = @"A String.";
NSString *string2 = @"A String.";
BOOL result = [string1 isEqualToString:string2];

The statement really will evaluate to "yes", because the values of the objects that string1 and string2 point to are equal. A more general method - compare: allows you to determine if a string is equal to the receiver of this method, or whether the string would come before or after the receiver string (as in the ordering of a dictionary, lexical ordering). The return type of compare: is a custom Cocoa data type called NSComparisonResult, which has three possible values: NSOrderedAscending, NSOrderedSame, NSOrderedDescending (these are just constants defined in the Foundation Framework equal to the integers -1, 0, and 1 respectively. So, we could use compare in the following fashion:

NSString *string1 = @"aardvark";
NSString *string2 = @"tarsier";

BOOL result = [string1 compare:string2] == NSOrderedAscending;

Beacuase string2 comes after string1 in the alphabet, the message to string1 will return the value NSOrderedAscending, which we compare with NSOrderedAscending using the equality operator, and get "yes" as the value of result. This is equivalent to saying string2 is greater than string1.

By substituting NSOrderedSame or NSOrderedDescending in place of NSOrderedAscending, we can check to see whether the receiver (string1) is the same as the argument (string2), or whether string2 appears sooner in the alphabet than string1.

In this system, uppercase letters are "less" than lowercase letters. So the following would evaluate to "yes":

NSString *string1 = @"Aardvark";
NSString *string2 = @"aardvark";

BOOL result = [string1 compare:string2] == NSOrderedAscending;

This is because "Aardvark" occurs before "aardvark" lexically (think in terms of the order of words in a dictionary). If you want to compare strings without regard to case sensitivity, then the -caseInsensitiveCompare: is the method for you. This method in use would look like (using the same strings as the previous example):

BOOL result = [string1 caseInsensitiveCompare:string2] == NSOrderedSame;

And result would be given the value "yes" because the statement evaluates as true.

These are some of the basic methods available for string comparison for you to use. If your needs are more demanding than what is we covered here, take a closer look at the class documentation, which details many more string comparison methods that give you more flexibility and options.

Finding strings within strings

NSString provides some methods that allow us to search strings for substrings. All of the string search methods return a special data type defined in the Foundation Framework known as NSRange. NSRange is a just C struct with two components, a starting index, and a length.

The way ranges work is like this: If we had a string with 100 characters (elements), then the range {49, 50} specifies a substring whose first element is the 49th character of the parent string, and includes the following 50 elements -- that is, the last half of the parent string (remember, strings are arrays in their most fundamental form, and counting always starts from 0).

In the next few examples, we will be using the following string:

NSString *theString = @"Okay, enough about ranges.";

Suppose we want to find where in the parent string the substring "about" can be found. The method we invoke is -rangeOfString:, and here is a snippet of code we could use to illustrate the way it works:

NSString *theString = @"Okay, enough about ranges";
NSString *substring = @"about";
NSRange range = [theString rangeOfString:substring];
int location = range.location;
int length = range.length;
NSString *displayString = [[NSString alloc] initWithFormat:@"Location: %i, length: %i",
location, length];
[textField setStringValue:displayString];

Note, that NSRange is not a class, it is a C structure, so we do not type the NSRange variable range as we do classes using the pointer-star (*); it is simply NSRange. In the previous example, the range returned by the search method is where in the parent string, theString, we can find the substring; thus firstElement is 13, and length is just the length of the substring, 5. If the substring cannot be found in the parent string, then a range with length zero is returned, indicating failure.

Additionally, note how we access the elements of a C structure. NSRange is defined as the following structure:

typedef struct _NSRange {
    unsigned int location;
    unsigned int length;
} NSRange;

Recall from C that components of a struct variable are accessed using the variableName.component construct. So, in our example above, we access the location and length components of range in the same way: range.location, and range.length.

Extracting substrings from strings

Three methods that allow us to extract substrings from a parent string are:

  • -substringToIndex:
  • -substringWithRange:
  • -substringFromIndex: (which respectively take a substring from the beginning, middle, and end of a parent string.)

The first method, -substringToIndex:, returns a new string which is composed of the characters from the beginning of the receiver string up to, but not including, the character at the specified index. This might be used in the following way:

NSString *aString = @"Running out of ideas for strings.";
NSString *substring = [aString substringToIndex:7];

The result of this operation would be that substring now points to the string object @"Running". The method -substringFromIndex: works in the same way, except now the substring starts at the specified index of the receiver (including the character at the index), and includes all the characters to the end of the receiver. So if we wanted to get the substring "strings" out of aString, we would do the following:

NSString *substring = [aString substringFromIndex:25];

Finally, we have the method which lets us arbitrarily extract a substring from anywhere within the parent string-substringWithRange:. The argument to this method is -- as conveniently indicated by the method name (I love that about Objective-C) -- an NSRange. So, we could get the string "ideas" out of the parent string, aString this way:

NSString *substring = [aString substringWithRange:NSMakeRange(15, 5)];

Here the range starts with the 15th character, "i", and extends to include the next four characters, giving us a length of 5, "ideas".

Farewell

We've seen in this column just the fundamentals of working with string objects in Cocoa. Hopefully there is enough here to keep you busy, in addition to equipping you with the confidence to go and explore the more advanced methods of NSString. In the next column I will continue our discussion of strings by talking about how we work with paths, and I will also cover mutable strings and the NSMutableString class. Happy programming to you all! See you next time!

Michael Beam is a software engineer in the energy industry specializing in seismic application development on Linux with C++ and Qt. He lives in Houston, Texas with his wife and son.


Read more Programming With Cocoa columns.

Return to the Mac DevCenter.