See Also
Previous iPhone Friday
Hello and happy Friday. Today's collection of iPhone wallpapers continue our look at photos from around the neighborhood. Cheers!Previous iPhone Friday
(click an image for the full-size wallpaper)
NSString comparison and equality. Today we'll examine functions and methods for creating substrings of C strings and NSStrings.strncpy() function. There's a big gotcha when using strncpy() to copy a substring: it doesn't automatically add a null terminator to the destination. Here's an example of copying the first three characters of a C string into a fixed buffer:// copy substring from start of source
// using a fixed buffer
char const *source = "foobar";
char buffer[4]; // make sure buffer includes
// space for null terminator
strncpy(buffer, source, 3); // copy first 3 chars from source
buffer[3] = '\0'; // remember to add null terminatorUsing a dynamic buffer is similar, but requires explicit memory management.// copy substring from start of source
// using a dynamic buffer
char const *source = "foobar";
char *buffer = malloc(4 * sizeof(char)); // make sure buffer includes
// space for null terminator
if ( ! buffer) {
// must handle allocation failure
}
strncpy(buffer, source, 3); // copy first 3 chars from source
buffer[3] = '\0'; // remember to add null terminator
// use buffer ...
// don't forget to free() buffer when done
free(buffer);You can make this a little more compact by using calloc() instead of malloc(). The calloc() function allocates memory using malloc(), then clears all the bytes to zero. As long as you make sure to include an extra byte at the end, your new substring will be null terminated:// copy substring from start of source
// using a dynamic buffer
// allocated with calloc()
char const *source = "foobar";
char *buffer = calloc(4, sizeof(char)); // make sure buffer includes
// space for null terminator
if ( ! buffer) {
// handle allocation failure
}
strncpy(buffer, source, 3); // copy first 3 chars from source
// last char in buffer is already '/0'
// use buffer ...
// don't forget to free() buffer when done
free(buffer);There's not a huge difference between malloc() and calloc(), so choose whichever one you're more used to using, or use calloc() if you don't have a strong preference. The cost of clearing a range of memory to zeros is so tiny as to not be worth considering in most circumstances, and knowing that your buffer is initialized to zeros can be handy.// C strings are pointers char const *string = "foobar"; NSLog(@"'%s'", string); // prints out 'foobar' char const *substring = string + 3; NSLog(@"'%s'", substring); // prints out 'bar'You can add an integer value to the C string pointer to get a pointer to the middle of the source string -- just be careful not to go off the end of the string! If you only need the substring for a short period of time, or if you know that the source string will live longer than the substring and never change, it's safe to simply create a substring this way. However, you can introduce weird bugs if you get this wrong. When in doubt, copy the substring to a new buffer:
// create a substring from the middle of a string
char const *source = "foobar";
char const *substringSource = source + 3;
size_t charCount = strlen(substringSource) + 1;
char *buffer = calloc(charCount, sizeof(char));
if ( ! buffer) {
// handle allocation failure
}
strcpy(buffer, substringSource);
// use buffer ...
free(buffer);Here we calculate the starting point by simply adding 3 to the string pointer source. Then we figure out the number of chars we need to allocate using the strlen() function, remembering to add 1 for the null terminator character. After allocating memory, the strcpy() function copies all the characters from substringSource into buffer. Unlike strncpy(), strcpy() will copy the null terminator, so this code will be the same whether we use calloc() or malloc() to allocate the buffer.strncpy() to copy just the characters you need.NSStrings.NSStringsNSString. First we'll look at taking a substring from the start of an NSString:// create a substring from the start of source NSString *source = @"foobar"; NSString *substring = [source substringToIndex:3]; // substring is "foo"The substring returned by
-substringToIndex: is autoreleased. You should -retain or -copy it if you need to hold on to it.NSString and goes to the end:// create a substring to the end of source NSString *source = @"foobar"; NSString *substring = [source substringFromIndex:3]; // substring is "bar"Finally, the general purpose way to create a substring of an
NSString is the -substringWithRange: method, which uses an NSRange structure, which is defined something like this:// NSRange structure
struct NSRange {
NSUInteger location;
NSUInteger length;
}When used with -substringWithRange: method, the NSRange's location field is the zero-based index of the first character to be included in the substring, and the length field is the number of characters to include in the substring. Here are some examples:// -substringWithRange: examples NSString *source = @"foobar"; NSRange range; range.location = 0; range.length = 3; NSString *frontHalf = [source substringWithRange:range]; // frontHalf is "foo" range.location = 3; range.length = 3; NSString *backHalf = [source substringWithRange:range]; // backHalf is "bar" range.location = 2; range.length = 2; NSString *middle = [source substringWithRange:range]; // middle = "ob"One word of caution: if the range you give falls outside the receiver (the source string), this method will raise an
NSRangeException.NSRange is fairly verbose; it's generally more convenient to use the NSMakeRange() function to create the NSRange structure instead.// NSMakeRange() example NSString *source = @"foobar"; NSString *frontHalf = [source substringWithRange:NSMakeRange(0, 3)]; // frontHalf is "foo"
NSString uses UTF-16 encoding. Although UTF-16 is a variable length encoding like UTF-8, characters from the basic multilingual plane are all two bytes (one word) in length. If you're certain that your NSString contains only basic multilingual plane characters, then methods like -length and -substringWithRange: will work exactly as you expect them. However, if your NSString includes characters outside the basic multilingual plane, it will contain surrogate pairs, which are multi-word sequences that represent a single character. You'll find that -length tells you the number of words rather than logical characters, and if you're not careful, methods like -substringWithRange: can split a surrogate pair in half, leaving you with an invalidly encoded string.NSStrings.
NSStrings and other pointer types. Value types like ints always designate separate things in memory. In C and Objective-C, identity equality is determined by comparing pointer values using the == operator.// comparing two strings for identity
char const s1 = "foo";
char const s2 = s1;
if (s1 == s2) {
NSLog(@"s1 is identical to s2");
}
NSString *s3 = @"foo";
NSString *s4 = @"bar";
if (s3 != s4) {
NSLog(@"s3 is not identical to s4");
}strcmp() function. The strcmp() function compares the data of two C strings char by char; if two C strings represent the same sequence of char values in memory, they are equivalent and strcmp() returns zero.// checking two C strings for equal value
char const *s1 = "foo";
char const *s2 = "bar";
if (strcmp(s1, s2) == 0) {
NSLog(@"s1 is equivalent to s2");
} else {
NSLog(@"s1 is not equivalent to s2");
}In addition to checking for equivalence, strcmp() also categorizes the sort order of the two C strings. If the first argument comes before the second, a negative value is returned; if the first argument comes after the second, a positive value is returned. The strcmp() function uses a lexicographic comparison, which means that the comparison is strictly on the basis of the integer values of the chars in the C strings. For ASCII strings, the string "2" (ASCII code 50) comes before "A" (ASCII code 64), which precedes "a" (ASCII code 97). Many sorting algorithms, including the qsort() function in the C standard library, require a function like strcmp().// using strcmp() result
int compareResult = strcmp(s1, s2);
if (compareResult < 0) {
NSLog(@"s1 comes before s2");
} else if (compareResult > 0) {
NSLog(@"s1 comes after s2");
}strncmp() function will compare a limited number of characters, stopping early if it encounters a null terminator in either string. Thus these two strings are equivalent when the first three characters are compared:if (strncmp("foo", "fooey", 3) == 0) {
NSLog(@"both start with foo");
}
// prints "both start with foo"strncmp(), short strings come first:if (strncmp("foo", "fooey", 5) < 0) {
NSLog(@"foo comes before fooey");
}
// prints "foo comes before fooey"strcasecmp(). Most modern Unix and Linux systems (including iOS and Mac OS X) have strcasecmp() available in the standard library. Older Unix systems and other operating systems may call this function stricmp() or strcmpi(). There is usually also a length limited version called strncasecmp() or strnicmp().// case insensitive comparison
char const *s = "<HTML><HEAD>...";
if (strncasecmp(s, "<html>", 6) == 0) {
NSLog(@"looks like HTML");
}strcmp() function was created in the era when most computers used ASCII or other simple single byte encodings. In ASCII, there is only one byte sequence that represents any particular character sequence. This isn't true of many modern encodings, including Unicode. The Unicode character set contains both accented characters such as "é" as well as a combining accent character "´", so there are two ways to represent "é" in UTF-8 encoding:| Address | 64 | 65 | 66 |
|---|---|---|---|
| Character | 'é' | ||
| Value | 195 | 169 | |
| Character | 'e' | '´' | |
| Value | 101 | 204 | 129 |
strcmp() will not see these two strings as equivalent. Accounting for this requires performing normalization on the Unicode characters in the string before doing the comparison. Unicode has several different types of normalization, which we won't dive into here. If you need to do a lot of low level processing of UTF-8 or other Unicode encoded text, you should look at the International Components for Unicode, a library of C functions for Unicode processing that is included as part of iOS. Better yet, in most cases you should use NSStrings when working with text.NSString equalityNSString class defines the -isEqualToString: instance method for testing if an NSString is equivalent to another NSString:// compare two NSStrings
NSString *s1 = @"foo";
NSString *s2 = @"bar";
if ( [s1 isEqualToString:s2] ) {
NSLog(@"The strings are equivalent.");
}You can also use the -isEqual: instance method defined by NSObject to compare two NSStrings, or to compare an NSString with any other object:// compare two NSStrings using -isEqual:
NSString *s1 = @"foo";
NSString *s2 = @"bar";
if ( [s1 isEqual:s2] ) {
NSLog(@"The strings are equivalent.");
}The difference between the two methods is in their declarations. The -isEqualToString: method is only for comparing one NSString to another; it's declaration looks like:// declaration of -isEqualToString: - (BOOL)isEqualToString:(NSString *)aStringThe
-isEqual: method is for comparing any kind of NSObject to another object; it's declaration looks like:// declaration of -isEqual: - (BOOL)isEqual:(id)anObjectIt's possible to use
-isEqual: to compare an NSString with an object of a different type, such as an NSNumber:NSString *fiveString = @"5";
NSNumber *fiveNumber = [NSNumber numberWithInt:5];
if ( [fiveString isEqual:fiveNumber] ) {
NSLog(@"fiveString equals fiveNumber");
} else {
NSLog(@"Strings aren't equivalent to numbers, silly!");
}You might hope that the NSString "5" is equivalent to the NSNumber "5" but unfortunately they are not; the code above will print out "Strings aren't equivalent to numbers, silly!". In general, objects of different classes aren't considered to be equivalent with one common exception: immutable classes like NSString can be equivalent to their mutable subclasses (NSMutableString in this case) and vice versa.NSString *fiveString = @"5";
NSMutableString *fiveMutableString = [NSMutableString stringWithString:@"5"];
if ( [fiveString isEqual:fiveMutableString] ) {
NSLog(@"immutable and mutable strings can be equivalent");
}And since NSMutableString is a subclass of NSString, you can also use -isEqualToString: to compare them:if ( [fiveString isEqualToString:fiveMutableString] ) {
NSLog(@"immutable and mutable strings can be equivalent");
}-compare:-isEqual: or -isEqualToString:, you can also discover the relative order of two NSString objects using the -compare: family of methods. The -compare: method is very similar to the strcmp() method in C. The -compare: method returns a NSComparisonResult value, which is simply an integer value. Similar to strcmp(), -compare: will return zero if the two NSStrings are equivalent, though you can also use the constant NSOrderedSame instead of zero:// compare two NSStrings
NSString *s1 = @"foo";
NSString *s2 = @"bar";
if ( [s1 compare:s2] == NSOrderedSame] ) {
NSLog(@"s1 is equivalent to s2");
} else {
NSLog(@"s1 is not equivalent to s2");
}Like strcmp(), if the receiver of the -compare: message (the first NSString) comes before the first argument (the second NSString), negative one is returned; if the receiver comes after the first argument, positive one is returned. The constants NSOrderedAscending and NSOrderedDescending can be used instead of -1 and 1 respectively.// using NSComparisonResult
NSComparisonResult comparisonResult = [s1 compare:s2];
if (comparisonResult == NSOrderedAscending) {
NSLog(@"s1 comes before s2");
} else if (comparisonResult == NSOrderedAscending) {
NSLog(@"s1 comes after s2");
}-compare:NSString objects in a case insensitive manner, use -compare:options: with the NSCaseInsensitiveSearch flag.// case insensitive compare
NSString *s1 = @"foo";
NSString *s2 = @"FOO";
if ( [s1 compare:s2 options:NSCaseInsensitiveSearch] == NSOrderedSame) {
NSLog(@"s1 is equivalent to s2");
}Since case insensitive comparison is a common operation, NSString has a convenience method, -caseInsensitiveCompare: which does the same thing.// case insensitive compare
NSString *s1 = @"foo";
NSString *s2 = @"FOO";
if ( [s1 caseInsensitiveCompare:s2] == NSOrderedSame) {
NSLog(@"s1 is equivalent to s2");
}-compare:NSString is pretty smart about Unicode and automatically understands things like Unicode combining characters. For instance, you can represent é two ways, but NSString knows that they represent equivalent strings:// comparing equivalent Unicode strings
NSString *eAcute = @"\u00e9"; // single character 'é'
NSString *ePlusAcute = @"e\u0301"; // 'e' + combining '´'
if ( [eAcute isEqualToString:ePlusAcute] ) {
NSLog(@"'é' is equivalent to 'e' + '´'");
}This can be surprising if you've only worked with ASCII or other single byte encodings. With NSString, you can't assume that equivalent strings have the same length and character sequence. Usually you don't care about the Unicode representation, but occasionally it's important. You can use the NSLiteralSearch flag along with -compare:options: to do a lexicographic comparison that compares strings character value by character value.// lexicographic comparison of Unicode strings
if ( [eAcute compare:ePlusAcute options:NSLiteralSearch] != NSOrderedSame) {
NSLog(@"'é' is not lexicographically equivalent to 'e' + '´'");
}-compare:options: method are bit flags. You combine them using the bitwise or operator (|).// using multiple options
NSString *eAcute = @"\u00e9"; // 'é'
NSString *capitalEAcute = @"\u00c9"; // 'É'
if ( [eAcute compare:capitalEAcute
options:NSCaseInsensitiveSearch | NSLiteralSearch]
!= NSOrderedSame)
{
NSLog(@"'é' is equivalent to 'É'");
}NSString objects, you can use -compare:options:range: method and specify an NSRange structure. The NSRange structure is composed of two parts: a starting location field named loc and a length field named len. Usually it's convenient to use the NSMakeRange() function to generate the NSRange.// compare substrings
NSString *s1 = @"foo";
NSString *s2 = @"fooey";
if ( [s1 compare:s2
options:0
range:MakeRange(0, 3)] == NSOrderedSame)
{
NSLog(@"both strings start with 'foo'");
}You pass in zero for the options to use the default comparison. -compare:options:range: is similar to strncmp() with one important difference: the NSRange you give must fall completely inside the receiver (the first string) or an NSRangeException will be thrown.-compare: methods use the current locale to determine the ordering of two strings. The current locale is controlled by the user when they set their language and region for their iOS device. Most of the time you should respect the user's settings, but sometimes it's appropriate to compare strings using a fixed locale. Perhaps your app teaches French vocabulary and you want your French word list to sort in standard French order whether the user's phone is set to English, German or Japanese. In French, accented letters at the end of a word sort before accented letters earlier in a word, thus "coté" should come before "côte". If you use the default locale, the result of comparing "coté" and "côte" varies but will probably not give you the correct ordering.// compare using default locale
NSString *coteAcute = @"cot\u00e9"; // "coté"
NSString *coteCircumflex = @"c\u00f4te"; // "côte"
if ( [coteAcute compare:coteCircumflex] == NSOrderedAscending) {
NSLog(@"Not using a French locale");
}To remedy this, you can set the locale explicitly when you do your comparison:// compare using specific locale
NSLocale *frenchLocale = [[[NSLocale alloc] initWithLocaleIdentifier:@"fr_FR"] autorelease];
NSComparisonResult comparisonResult = [coteAcute compare:coteCircumflex
options:0
range:NSMakeRange(0, 4)
locale:frenchLocale];
if (comparisonResult == NSOrderedDescending) {
NSLog(@"Using a French locale");
}NSStrings. Next time, we'll look at slicing and dicing strings by creating substrings.
![]() |
Places? (NEW) Category: Entertainment Released Jan 4, 2009 Version: 1.0 1.3 MB $0.99 USD |
![]() |
People? (NEW) Category: Entertainment Released Dec 28, 2009 Version: 1.0 1.3 MB $0.99 USD |
![]() |
Orchard's Craps (NEW) Category: Games Released Oct 2, 2009 Version: 1.0 2.9 MB $0.99 USD |
![]() |
Hi Ka Flash (NEW) Category: Education Released Aug 15, 2009 Version: 1.0 1.2 MB $0.99 USD |
![]() |
Fridgemags (NEW) Category: Entertainment Released Jul 24, 2009 Version: 1.0 2.3 MB $0.99 USD |
![]() |
Animals? (UPDATED) Category: Entertainment Released Mar 17, 2009 Version: 1.1 1.4 MB $0.99 USD |