Hostingheaderbarlogoj
Join InMotion Hosting for $3.49/mo & get a year on Tuts+ FREE (worth $180). Start today.
Advertisement

Objective-C Succinctly: Data Types

by
Gift

Want a free year on Tuts+ (worth $180)? Start an InMotion Hosting plan for $3.49/mo.

This post is part of a series called Objective-C Succinctly.
Objective-C Succinctly: Hello Objective-C
Objective-C Succinctly: Properties

Objective-C has two categories of data types. First, remember that Objective-C is a superset of C, so you have access to all of the native C data types like char, int, float, etc. Objective-C also defines a few of its own low-level types, including a Boolean type. Let's call all of these "primitive data types."

Second, Objective-C provides several high-level data structures like strings, arrays, dictionaries, and dates. These high-level data types are implemented as Objective-C objects, so you'll see many of the same object-oriented constructs from the previous chapter. Since these are all defined in the Foundation framework, we'll call them "foundation data structures."

Figure 14 Our two categories of data types

Our two categories of data types

This chapter covers both primitive data types and the most important foundation data structures. By the end of this chapter, you should have a solid grasp of every data structure you could possibly need for your Objective-C programs.


Displaying Values

In addition to data types, we'll also learn a lot more about NSLog() string formatting in this chapter. This will let us display variables of all sorts in the Xcode console, which is an indispensable skill for debugging applications.

As we saw in the previous chapter, NSLog() can be called with a format string. Inside of the format string, you use the % symbol to designate placeholder values, and NSLog() will fill them in with values passed as additional parameters. For example, the %@ in the following code is replaced with the aName variable:

NSLog(@"Hello %@, my name is HAL.", aName);

The %@ is used as a placeholder for objects (Objective-C strings are implemented as objects), but primitive data types use their own format specifiers, which will be covered in their respective sections.


Primitive Data Types

The first half of this chapter looks at the native Objective-C data types and discusses how to display them using NSLog() format strings. The size of the data types presented in this section is system-dependent-the only way to truly know how big your data types are is to use the sizeof() function. For example, you can check the size of a char with the following:

NSLog(@"%lu", sizeof(char));

This should output 1, which means that char takes up 1 byte of memory. The %luplaceholder is for unsigned long integers (discussed in more detail later), which is the return type for sizeof(). Upcoming sections discuss the most common sizes for Objective-C data types, but remember that this may differ from your system.

Booleans

Objective-C programs use the BOOL data type to store Boolean values. Objective-C also defines its own true and false keywords, which are YES and NO, respectively. To display BOOL values via NSLog(), use %i in the format string:

BOOL isHuman = NO;
NSLog(@"It's alive: %i", isHuman);

The %i specifier is used to display integers, so this should output It's alive: 0.

Technically, BOOL is a macro for the signed char type (discussed in the next section). This means that BOOL variables can store many more values than just YES and NO, which are actually macros for 1 and 0, respectively. However, most developers will never use this extra functionality, since it can be a source of frustrating bugs in conditional statements:

BOOL isHuman = 127;
if (isHuman) {
	// This will execute.
	NSLog(@"isHuman is TRUE");
}
if (isHuman == YES) {
	// But this *won't* execute.
	NSLog(@"isHuman is YES");
}

Any value greater than 0 will evaluate to true, so the first condition will execute, but the second will not because 127 != 1. Depending on how you're using your BOOL variables, this may or may not be a desirable distinction.

Chars

Objective-C uses the same char data type as ANSI C. It denotes a single-byte signed integer, and can be used to store values between -128 and 127 or an ASCII character. To display a char as an integer, just use the generic %i specifier introduced in the previous code sample. To format it as an ASCII character, use %c:

char letter = 'z';
NSLog(@"The ASCII letter %c is actually the number %i", letter, letter);

As with all integer data types, it's possible to allocate an unsigned char, which can record values from 0 to 255. Instead of the %i specifier, you should use %u as a placeholder for unsigned integers:

unsigned char tinyInt = 255;
NSLog(@"The unsigned char is: %u", tinyInt);

Short Integers

Short integers are 2-byte signed integers and should be used for values between -32768 and 32767. To display them with NSLog(), use the %hi specifier (the h is a "modifier" for the same %i used in the previous two sections). For example:

short int littleInt = 27000;
NSLog(@"The short int is: %hi", littleInt);

Unsigned shorts can be created the same way as unsigned chars and can hold up to 65535. Again, the u in %hu is the same one in %u for generic unsigned integers:

unsigned short int ulittleInt = 42000;
NSLog(@"The unsigned short integer is: %hu", ulittleInt);

"Normal" Integers

Next on the list is int, which is a 4-byte integer on most systems. Again, remember that data type size is system-dependent-the only way to know for sure how big your data types are is to use the sizeof() function:

NSLog(@"%lu", sizeof(int));

If your int type is indeed 4 bytes, it can hold values between -2147483648 and 2147483647.

int normalInt = 1234567890;
NSLog(@"The normal integer is: %i", normalInt);

This also means that the unsigned version can record 04294967295.

Long Integers

If int isn't big enough to meet your needs, you can move up to the long int data type, which is 8 bytes on most modern systems. This is large enough to represent values between -9223372036854775808 and 9223372036854775807. Long integers can be displayed via NSLog() by prepending the letter l to the %i or %u specifiers, as shown in the following code:

long int bigInt = 9223372036854775807;
NSLog(@"The big integer is: %li", bigInt);

unsigned long int uBigInt = 18446744073709551615;
NSLog(@"The even bigger integer is: %lu", uBigInt);

18446744073709551615 is the maximum value for the unsigned version, which is hopefully the largest integer you'll ever need to store.

The idea behind having so many integer data types is to give developers the power to balance their program's memory footprint versus its numerical capacity.

Floats

Objective-C programs can use the float type for representing 4-byte floating point numbers. Literal values should be suffixed with f to mark the value as single precision instead of a double (discussed in the next section). Use the %f specifier to output floats with NSLog():

float someRealNumber = 0.42f;
NSLog(@"The floating-point number is: %f", someRealNumber);

You can also specify the output format for the float itself by including a decimal before the f. For example, %5.3f will display 3 digits after the decimal and pad the result so there are 5 places total (useful for aligning the decimal point when listing values).

While floating-point values have a much larger range than their fixed-point counterparts, it's important to remember that they are intrinsically not precise. Careful consideration must be paid to comparing floating-point values, and they should never be used to record precision-sensitive data (e.g., money). For representing fixed-point values in Objective-C, please see NSDecimalNumber in the the Foundation Data Structures section.

Doubles

The double data type is a double-precision floating-point number. For the most part, you can treat it as a more accurate version of float. You can use the same %f specifier for displaying doubles in NSLog(), but you don't need to append f to literal values:

double anotherRealNumber = 0.42;
NSLog(@"The floating-point number is: %5.3f", anotherRealNumber);

Structs

Objective-C also provides access to C structs, which can be used to define custom data structures. For example, if you're working on a graphics program and interact with many 2-dimensional points, it's convenient to wrap them in a custom type:

typedef struct {
	float x;
	float y;
} Point2D;

The typedef keyword tells the compiler we're defining a new data type, struct creates the actual data structure, which comprises the variables x and y, and finally, Point2D is the name of the new data type. After declaring this struct, you can use Point2D just like you would use any of the built-in types. For instance, the following snippet creates the point (10.0, 0.5) and displays it using our existing NSLog() format specifiers.

Point2D p1 = {10.0f, 0.5f};
NSLog(@"The point is at: (%.1f, %.1f)", p1.x, p1.y);

The {10.0f, 0.5f} notation is called a compound literal, and it can be used to initialize a struct. After initialization, you can also assign new values to a struct's properties with the = operator:

p1.x = -2.5f;
p1.y = 2.5f;

Structures are important for performance-intensive applications, but they sometimes prove difficult to integrate with the high-level Foundation data structures. Unless you're working with 3-D graphics or some other CPU-heavy application, you're usually better off storing custom data structures in a full-fledged class instead of a struct.

Arrays

While Objective-C provides its own object-oriented array data types, it still gives you access to the low-level arrays specified by ANSI C. C arrays are a contiguous block of memory allocated when they're declared, and all of their elements must be of the same type. Unlike C# arrays, this means you need to define an array's length when it's declared, and you can't assign another array to it after it's been initialized.

Because there is no way for a program to automatically determine how many elements are in an array, there is no convenient NSLog() format specifier for displaying native arrays. Instead, we're stuck with manually looping through each element and calling a separate NSLog(). For example, the following code creates and displays an array of 5 integers:

int someValues[5] = {15, 32, 49, 90, 14};
for (int i=0; i<5; i++) {
	NSLog(@"The value at index %i is: %i", i, someValues[i]);
}

As you can see, C arrays look much like atomic variables, except you have to provide their length in square brackets ( [5] ). They can be initialized with the same compound literal syntax as structs, but all the values must be of the same type. Individual elements can be accessed by passing the item number in square brackets, which is common in most programming languages. In addition, you can access elements via pointers.

Pointers provide a low-level way to directly access memory addresses in a C program. And, since C arrays are just contiguous blocks of memory, pointers are a natural way to interact with items in an array. In fact, the variable holding a native array is actually a pointer to the first element in the array.

Pointers are created by prefixing the variable name with an asterisk (*). For example, we can create a second reference to the first element in the someValues array with the following code:

int someValues[5] = {15, 32, 49, 90, 14};
int *pointer = someValues;

Instead of storing an int value, the *pointer variable points to the memory address containing the value. This can be visualized as the following:

Figure 15 Pointer to the first element of an array

Pointer to the first element of an array

To get the underlying value out of the memory address, we need to dereference the pointer using the asterisk operator, like so:

NSLog(@"The first value is: %i", *pointer);

This should display 15 in your output panel, since that is the value stored in the memory address pointed to by the pointer variable. So far, this is just a very confusing way to access a normal (non-pointer) int variable. However, things get much more interesting when you start moving pointers around with the ++ and -- operators. For example, we can increment the pointer to the next memory address as follows:

pointer++;
NSLog(@"The next value is: %i", *pointer);

Since an array is a contiguous block of memory, the pointer will now rest at the address of the second element of the array. As a result, the NSLog() call should display 32 instead of 15. This can be visualized as the following:

Figure 16 Incrementing the pointer to the second element of an array

Incrementing the pointer to the second element of an array

Pointers provide an alternative way to iterate through an array. Instead of accessing items via the square brackets (e.g., someValues[i]), you can simply increment the pointer and dereference it to get the next value:

for (int i=0; i<5; i++) {
	pointer++;
	NSLog(@"The value at index %i is: %i", i, *pointer);
}

Pointers have innumerable uses in high-performance applications, but in reality, you probably won't need to use pointers with native arrays unless you're building a data-intensive application that is seriously concerned with speed.

However, pointers are still very important to Objective-C programs because every object is referenced through a pointer. This is why all of the data structures in the upcoming Foundation Data Structures section are declared as pointers (e.g., NSNumber *someNumber, not NSNumber someNumber).

Void

The void type represents the absence of a value. Instead of typing variables, void is used with functions and methods that don't return a value. For example, the sayHello method from the previous chapter didn't return anything, and it was thus defined with the void data type:

- (void)sayHello;

Nil and NULL

The nil and NULL keywords are both used to represent empty pointers. This is useful for explicitly stating that a variable doesn't contain anything, rather than leaving it as a pointer to its most recent memory address.

There is, however, a strict distinction between the two. The nil constant should only be used as an empty value for Objective-C objects-it should not be used to for native C-style pointers (e.g., int *somePointer). NULL can be used for either primitive pointers or Objective-C object pointers, though nil is the preferred choice.


Primitive Data Type Summary

The first half of this chapter introduced the primitive data types available to Objective-C programmers. We also took a brief look at pointers and the nil and NULL keywords.

It's important to remember that the value stored in a variable is completely independent from how it's interpreted. unsigned ints can be interpreted as signed ints without changing the variable in any way. That's why it's so important to make sure you're using the right format string in NSLog(). Otherwise, you'll be left wondering why your unsigned variables look like they're storing negative numbers. As we'll see in the next section, this isn't as much of a problem with object-oriented data types.

The remainder of this chapter focuses on the Foundation framework, which defines several object-oriented data structures that all Objective-C developers should be familiar with.


Foundation Data Structures

Primitive data types are essential to any Objective-C program, but it's often tedious to work on such a low level. The Foundation framework abstracts these native types into high-level, object-oriented tools, which lets you focus on how your application works instead of how to store your data.

The data structures that follow are common to most high-level programming languages, but since it's Objective-C, they have unique method names for manipulating the data they contain. The goal of this section is to introduce you to the most important aspects of the core classes defined in the Foundation framework, rather than to provide a comprehensive API reference. If you're looking for the latter, please visit the Foundation Framework Reference.

NSNumber

NSNumber is a generic container for numeric types (i.e. BOOL, char, short, int, long, float, and double ). It lets you take one of the primitive types discussed earlier in this chapter and interact with it in an object-oriented fashion. This is called boxing, and it's an essential tool for integrating Objective-C with C and C++ libraries.

NSNumber provides several convenient methods to convert to and from primitive values. For example, you can store an integer in NSNumber with the following:

int someInteger = -27;
NSNumber *someNumber = [NSNumber numberWithInt:someInteger];

Likewise, float s can be created with numberWithFloat:, double s can be created with numberWithDouble:, BOOL s can be created with numberWithBool:, etc., The recorded value can be accessed with the corresponding accessor method:

NSLog(@"The stored number is: %i", [someNumber intValue]);

Accessors for other primitives follow the same pattern: floatValue, doubleValue, boolValue, etc.

Remember that the %@ specifier is used as a placeholder for objects. Most classes in the Foundation framework define their own display formats. NSNumber will simply display its stored value, so the following format string will output the exact same thing as the previous snippet. Not having to figure out which specifier to use is one of the convenient perks of using NSNumber.

NSLog(@"The stored number is: %@", someNumber);

Note that NSNumber is an immutable type, so you'll have to create a new instance if you need to change the stored value. This may seem like a lot of overhead, but compared to everything else going on in an Objective-C program, it's not actually that much of a performance hit. Of course, if it becomes a problem, you can always fall back to the native C primitives.

One of the other perks of NSNumber is the ability to set a variable to nil to indicate an empty value. There is no way to do this with primitive numerical values.

NSDecimalNumber

The NSDecimalNumber class is Objective-C's fixed-point class. It can represent much more precise numbers than float or double, and is thus the preferred way to represent money or other precision-sensitive data. The easiest way to create an NSDecimalNumber is to use the decimalNumberWithString: method, like so:

NSDecimalNumber *subtotal = [NSDecimalNumber
decimalNumberWithString:@"10.99"];

Since NSDecimalNumber uses more precise arithmetic algorithms than floating-point numbers, you can't use the standard +, -, *, or / operators. Instead, NSDecimalNumber provides its own methods for all of these operations:

  • - decimalNumberByAdding:(NSDecimalNumber *)aNumber
  • - decimalNumberBySubtracting:(NSDecimalNumber *)aNumber
  • - decimalNumberByMultiplyingBy:(NSDecimalNumber *)aNumber
  • - decimalNumberByDividingBy:(NSDecimalNumber *)aNumber

Like NSNumber, NSDecimalNumber is an immutable type, so all of these methods return a new instance of NSDecimalNumber. For example, the next snippet multiplies a product's price by a discount percentage:

NSDecimalNumber *subtotal = [NSDecimalNumber
							 decimalNumberWithString:@"10.99"];
NSDecimalNumber *discount = [NSDecimalNumber
							 decimalNumberWithString:@".25"];
NSDecimalNumber *total = [subtotal decimalNumberByMultiplyingBy:discount];
NSLog(@"The product costs: $%@", total);

However, if you run this code sample, you'll notice that it outputs a few extra places after the decimal. Fortunately, NSDecimalNumber provides detailed options for configuring its rounding behavior. This is the primary reason to use NSDecimalNumber over the primitive float or double data types. To define your rounding behavior, create an instance of NSDecimalNumberHandler with your desired parameters, and then pass it to NSDecimalNumber's arithmetic operations via the withBehavior parameter. The following configuration is useful for working with currencies:

NSDecimalNumberHandler *roundUp = [NSDecimalNumberHandler
	decimalNumberHandlerWithRoundingMode:NSRoundUp
								   scale:2
						raiseOnExactness:NO
						 raiseOnOverflow:NO
						raiseOnUnderflow:NO
					 raiseOnDivideByZero:YES];

NSDecimalNumber *roundedTotal = [subtotal
	decimalNumberByMultiplyingBy:discount
					withBehavior:roundUp];

NSLog(@"The product costs: $%@", roundedTotal);

The NSRoundUp argument tells NSDecimalNumber operations to round up (the other options are NSRoundPlain, NSRoundDown, and NSRoundBankers). Next, the scale parameter defines the maximum number of digits after the decimal point (note that negative values will start removing significant figures to the left of the decimal point). The rest of the parameters define the exception handling behavior of NSDecimalNumber operations. In this case, we're telling it to ignore everything that could go wrong unless we try to divide by zero. Together, these arguments make sure that we always have two decimals in our currency values and that they are always rounded up.

Generally, an instance of NSDecimalNumber is only useful for interacting with other NSDecimalNumber objects, but you may occasionally need to convert them to another data type:

double totalAsDouble = [roundedTotal doubleValue];
NSString *totalAsString = [roundedTotal stringValue];

The stringValue method is particularly useful for exporting values to a database or some other persistent storage (NSDecimalNumber should never be stored as a double unless you really don't care about loss of precision). It's also worth mentioning that the Core Data framework does provide a native storage mechanism for NSDecimalNumber, although that's outside the scope of this book.

NSString

NSString is the immutable string class used by the vast majority of Objective-C programs. We've already seen it in action in the Hello, Objective-C chapter, but let's take a closer look at some of its methods. At heart, NSString is a glorified C array of integers representing characters. Its two most basic methods are:

  • -(NSUInteger)length - Return the number of characters in the string.
  • -(unichar)characterAtIndex:(NSUInteger)theIndex - Return the character at theIndex.

These two methods make it possible to iterate through individual characters in a string. For example:

NSString *quote = @"Open the pod bay doors, HAL.";
for (int i=0; i<[quote length]; i++) {
	NSLog(@"%c", [quote characterAtIndex:i]);
}

Yet the real power of NSString comes in its higher-level functionality. Some of the most common methods are described in the following list, but keep in mind that this list is far from complete.

  • +(id)stringWithFormat:(NSString *)format ... - Create a string using the same placeholder format as NSLog().
  • -(NSString *)stringByAppendingString:(NSString *)aString - Append a string to the receiving object.
  • -(NSString *)stringByAppendingFormat:(NSString *)format ... - Append a string using the same placeholder format as NSLog().
  • -(NSString *)lowercaseString - Return the lowercase representation of the receiving string.
  • -(NSString *)substringWithRange:(NSRange)aRange - Return a substring residing in aRange (see following example for usage).
  • -(NSRange)rangeOfString:(NSString *)aString - Search for aString in the receiving string and return the location and length of the result as an NSRange (see following example for usage).
  • -(NSString *)stringByReplacingOccurancesOfString:(NSString *)target withString:(NSString *)replacement - Replace all occurrences of target with replacement.

This last method is a good example of how the verbose nature of Objective-C method names makes programs self-documenting. It's long to type, but no one will mistake what you are trying to accomplish with this method. The following example demonstrates a few of these higher-level methods and shows you how to use NSRange, which is a struct containing location and length fields. NSMakeRange() is a convenience function defined by the Foundation framework for creating an NSRange.

NSString *quote = @"Open the pod bay doors, HAL.";
NSRange range = NSMakeRange(4, 18);
NSString *partialQuote = [quote substringWithRange:range];
NSLog(@"%@", partialQuote);

NSString *target = @"HAL";
NSRange result = [quote rangeOfString:target];
NSLog(@"Found %@ at index %lu. It's %lu characters long.",
	target, result.location, result.length);

NSString also has the ability to directly read and write the contents of a file, but we'll leave that until the second book of this series, iOS Succinctly.

NSMutableString

As you probably could have guessed, NSMutableString is the mutable counterpart of NSString. A mutable string is one that lets you change individual characters without generating an entirely new string. If you're making many small changes to a string, a mutable string is more efficient, since it changes the characters in place. An immutable string, on the other hand, would have to allocate a new string for each change.

NSMutableString is implemented as a subclass of NSString, so you have access to all of the NSString methods, along with the addition of a few new methods for manipulating the character array in place:

  • -(void)appendString:(NSString *)aString - Append aString to the end of the receiving string.
  • -(void)appendFormat:(NSString *)format ... - Append a string using the same placeholder format as NSLog().
  • -(void)insertString:(NSString *)aString atIndex (NSUInteger)anIndex - Insert a string into the specified index.
  • -(void)deleteCharactersInRange:(NSRange)aRange - Remove characters from the receiving string.
  • -(void)replaceCharactersInRange:(NSRange)aRange withString:(NSString *)aString - Replace the characters in aRange with aString.

Note that these methods all have void return types, whereas the corresponding NSString methods return NSString objects. This is indicative of the behavior of mutable strings: nothing needs to be returned, because the characters are manipulated in place.

// With immutable strings.
NSString *quote = @"I'm sorry, Dave. I'm afraid I can't do that.";
NSString *newQuote = [quote
					  stringByReplacingCharactersInRange:NSMakeRange(11, 4)
											  withString:@"Capt'n"];
NSLog(@"%@", newQuote);

// With a mutable string.
NSMutableString *mquote = [NSMutableString stringWithString:quote];
[mquote replaceCharactersInRange:NSMakeRange(11, 4)
					  withString:@"Capt'n"];
NSLog(@"%@", mquote);

As you can see in this sample, the basic workflow behind mutable strings is much different than immutable strings. Mutable string methods operate on the object, so you can use the same variable over and over, changing its contents on the fly. Immutable string methods need multiple variables; of course, you could assign the new string to the same variable over and over, but new strings would still be generated behind the scenes.

Sometimes it's hard to know when to use immutable versus mutable data types. Mutable strings generally have very specific use cases (e.g., a linguistic parser that operates on tokens), so if you're not sure if you need one, you probably don't. For something like the previous example, an immutable string would be more appropriate.

NSArray

Arrays are ordered collections of objects that let you maintain and sort lists of data. Like NSString, NSArray is immutable, so its contents cannot be changed without requesting an entirely new array. The most important NSArray methods are shown in the following list. Once again, this is merely a survey, not a comprehensive overview:

  • +(id)arrayWithObjects:(id)firstObject, ... - Create a new array by passing in a list of objects.
  • -(NSUInteger)count - Return the number of elements in the array.
  • -(id)objectAtIndex:(NSUInteger)anIndex - Return the element in the array at index anIndex.
  • -(BOOL)containsObject:(id)anObject - Return whether or not anObject is an element of the array.
  • -(NSUInteger)indexOfObject:(id)anObject - Return the index of the first occurrence of anObject in the array. If the object is not in the array, return the NSNotFound constant.
  • -(NSArray *)sortedArrayUsingFunction:(NSInteger (*)(id, id, void *))compareFunction context:(void *)context - Sort an array by comparing objects with a user-defined function (see the second example that follows for usage).

Note that all of these methods use the generic object type id for their arguments. Consequently, NSArray can only handle objects-it cannot be used with primitive data types. The practical function of classes like NSNumber should now be much clearer: they facilitate boxing. That is, they make it possible to use char, int, float, etc., with NSArray by wrapping them in an object-oriented container. For example, the following snippet shows how you can use NSArray to manage a list of float values:

NSNumber *n1 = [NSNumber numberWithFloat:22.5f];
NSNumber *n2 = [NSNumber numberWithFloat:8.0f];
NSNumber *n3 = [NSNumber numberWithFloat:-2.9f];
NSNumber *n4 = [NSNumber numberWithFloat:13.1f];
NSArray *numbers = [NSArray arrayWithObjects:n1, n2, n3, n4, nil];
NSLog(@"%@", numbers);

Compared to primitive C arrays, NSArray provides plenty of high-level functionality, but of course, it comes at a cost. Boxing can be an expensive operation for high-performance applications. Imagine a graphics program using tens of thousands of floats to represent vertices in 3-D space. Creating that many NSNumber objects just for the sake of NSArray compatibility is not an efficient use of memory or CPU cycles. In that kind situation, you're probably better off sticking with native C arrays and directly working with primitive data types.

The signature for the sortedArrayUsingFunction: method may look intimidating, but it's actually a relatively straightforward way to define a custom sort algorithm for an array. First, you need to define the sort function:

Included code sample: ArraySort

NSInteger sortFunction(id item1, id item2, void *context) {
	float number1 = [item1 floatValue];
	float number2 = [item2 floatValue];
	if (number1 < number2) {
		return NSOrderedAscending;
	} else if (number1 > number2) {
		return NSOrderedDescending;
	} else {
		return NSOrderedSame;
	}
}

This defines a very simple ascending sort, but it demonstrates the essential components of a sort function. The item1 and item2 arguments are the two items currently being compared. Since the values are boxed in an NSNumber, we need to pull out the values before comparing them. Then we do the actual comparison, returning NSOrderedAscending when item1 should be placed before item2, NSOrderedDescending when it should be after item2, and returning NSOrderedSame when they do not need to be sorted. We can use this sort function like so:

int main(int argc, const char * argv[]) {
	@autoreleasepool {

		NSNumber *n1 = [NSNumber numberWithFloat:22.5f];
		NSNumber *n2 = [NSNumber numberWithFloat:8.0f];
		NSNumber *n3 = [NSNumber numberWithFloat:-2.9f];
		NSNumber *n4 = [NSNumber numberWithFloat:13.1f];
		NSArray *numbers = [NSArray arrayWithObjects:n1, n2, n3, n4, nil];
		NSLog(@"%@", numbers);

		NSArray *sortedNumbers = [numbers
								  sortedArrayUsingFunction:sortFunction
												   context:NULL];
		NSLog(@"%@", sortedNumbers);

	}
	return 0;
}

The second NSLog() output should show the numbers in ascending order from -2.9 to 22.5. sortedNumbers is an entirely new array, and the numbers variable remains unaltered. They do, however, point to the same instances of n1, n2, n3, and n4.

NSMutableArray

NSMutableArray is the mutable counterpart of NSArray. It's possible to change items after the array has been allocated and to extend or shrink the array by an arbitrary number of elements. While not as efficient as NSArray, the ability to incrementally add or remove items makes NSMutableArray a common data structure in Objective-C applications. NSMutableArray is a subclass of NSArray, so both can be created, accessed, and sorted using the methods in the previous section, but they also provide a few extra methods for manipulating their contents:

  • +(id)arrayWithCapacity:(NSUInteger)numItems - Create an empty mutable array. The numItems argument is used as a size hint, so it should be roughly the number of initial items you plan to store.
  • -(void)addObject:(id)anObject - Add the given object to the end of the existing array.
  • -(void)insertObject:(id)anObject atIndex:(NSUInteger)anIndex - Insert the given object into the specified index.
  • -(void)removeObjectAtIndex:(NSUInteger)anIndex - Remove the object at the specified index.
  • -(void)removeAllObjects - Clear the array.
  • -(void)replaceObjectAtIndex:(NSUInteger)anIndex withObject:(id)anObject - Overwrite the object at anIndex with anObject.
  • -(void)exchangeObjectAtIndex:(NSUInteger)index1 withObjectAtIndex:(NSUInteger)index2 - Swap the locations of two objects in the array.

Note that most of these mutable methods are essentially "write" methods, whereas the methods discussed in the NSArray section are mostly "read" methods. In addition, the mutable sort methods are the same as NSArray, except they sort the array in place instead of generating a new array. These differences are much the same as NSString versus NSMutableString. A simple example demonstrating the use of NSMutableArray as a queue follows:

// Define some people.
NSString *n1 = @"HAL";
NSString *n2 = @"Dave";
NSString *n3 = @"Heywood";

// Initialize an empty queue.
NSMutableArray *queue = [NSMutableArray arrayWithCapacity:4];

// Add to the queue.
[queue addObject:n1];
[queue addObject:n2];
[queue addObject:n3];

// Remove from the queue.
NSLog(@"Removing %@ from queue.", [queue objectAtIndex:0]);
[queue removeObjectAtIndex:0];
NSLog(@"Removing %@ from queue.", [queue objectAtIndex:0]);
[queue removeObjectAtIndex:0];
NSLog(@"Removing %@ from queue.", [queue objectAtIndex:0]);
[queue removeObjectAtIndex:0];

NSSet and NSMutableSet

Sets also represent a collection of objects, but unlike arrays, they are unordered collections. In addition, all of their elements must be unique. If you don't care about the order of elements or you want to make sure you don't have any duplicates in the collection, you should use NSSet and NSMutableSet instead of an array. In addition, sets are optimized for membership checking, so if your code is asking a lot of questions like, "Is this object in this group?" you should definitely be using a set instead of an array.

Figure 17 Ordered arrays vs unordered sets

Ordered arrays vs. unordered sets

Data structures reflect the underlying relationships between their elements. The array interpretation of the previous figure could be something like, "Dave is in charge, then Heywood, then Frank, and finally HAL," whereas the set interpretation is more generic: "Dave, Heywood, Frank, and HAL are part of the crew."

Other than ordering, sets and arrays have very similar functions and APIs. Some of the most important methods are:

  • +(id)setWithObjects:(id)firstObject, ... - Create a new set by passing a list of objects.
  • +(id)setWithArray:(NSArray)anArray - Create a new set with the contents of an array. This is a simple way to remove duplicate items from an NSArray.
  • -(NSUInteger)count - Return the number of members in the set.
  • -(BOOL)containsObject:(id)anObject - Return YES if the specified object is a member of the set, NO otherwise. NSArray does have an identical method, but the NSSet version is more efficient.
  • -(NSArray *)allObjects - Return an NSArray containing all of the set's members.

You can iterate through the members of a set using Objective-C's fast-enumeration syntax, as demonstrated in the following sample. Note that since NSSet is unordered, there is no guarantee as to how the objects will appear during the iteration:

NSSet *crew = [NSSet setWithObjects:@"Dave", @"Heywood", @"Frank", @"HAL", nil];
for (id member in crew) {
	NSLog(@"%@", member);
}

The Foundation framework also provides a mutable version of NSSet called NSMutableSet. Like NSMutableArray, you can alter a mutable set after creating it. Some of these "write" methods are:

  • -(void)addObject:(id)anObject - Add the specified object to the set. Duplicate members will be ignored.
  • -(void)removeObject:(id)anObject - Remove the specified object from the set.
  • -(void)unionSet:(NSSet *)otherSet - Add each item in otherSet to the receiving set if it's not already a member.

Both the immutable and mutable versions of NSSet provide several other methods for logical operations like intersections and equality. Please see the official reference for more information.

NSDictionary and NSMutableDictionary

Dictionaries, also called associative arrays, are unordered associations of key-value pairs. It's possible to use any object as a key or a value, so dictionaries can be used for anything from dynamically assigning roles to objects to mapping string commands to functions.

Figure 18 Unordered key-value pairs

Unordered key-value pairs

Like strings, arrays, and sets, there is an immutable and a mutable version. Some of the most common methods for NSDictionary are:

  • +(id)dictionaryWithObjectsAndKeys:(id)firstValue, (id)firstKey, ... - Create a dictionary by passing key-value pairs as parameters. Every two objects in the parameter list define a pair, and the first object defines the value, while the second object defines the key for that value (see next example for usage).
  • -(NSUInteger)count - Return the number of entries in the dictionary.
  • -(id)objectForKey:(id)aKey - Return the object (value) associated with aKey, or nil if there is no entry for aKey.
  • -(NSArray *)allKeys - Return a new array containing all of the keys in the dictionary.
  • -(NSArray *)allValues - Return a new array containing all of the values in the dictionary.
  • -(NSArray *)allKeysForObject:(id)anObject - Return a new array containing all of the keys associated with anObject. Note that it's possible to have multiple keys associated with a single object, so keys must be returned as an array, not a single object.

The two core methods for NSMutableDictionary are described in the following list. Again, note that these are the "write" methods for the associated "read" methods of NSDictionary.

  • -(void)setObject:(id)anObject forKey:(id<NSCopying>)aKey - Add a new key-value pair to the dictionary. The aKey argument must conform to the NSCopying protocol (refer to the Protocols chapter for more information). All of the objects we've discussed so far conform to this protocol, so you don't need to worry about it unless you're using custom classes as keys.
  • -(void)removeObjectForKey:(id)aKey - Remove the entry using aKey as its key.

Like NSSet, dictionaries can be iterated over using the fast-enumeration syntax, as demonstrated here:

NSMutableDictionary *crew = [NSMutableDictionary dictionaryWithObjectsAndKeys:@"Dave", @"Capt'n",
							 @"Heywood", @"Scientist",
							 @"Frank", @"Engineer", nil];

[crew setObject:@"HAL" forKey:@"Crazy Computer"];
[crew removeObjectForKey:@"Engineer"];

for (id role in crew) {
	NSLog(@"%@: %@", role, [crew objectForKey:role]);
}

This should output the following in your console, although the items may appear in a different order:

Scientist: Heywood
Crazy Computer: HAL
Capt'n: Dave

The Id Data Type

While not technically a part of the Foundation framework, this is an appropriate time to introduce the id type, which is the generic object data type. It can hold a pointer to any Objective-C object, regardless of its class. This makes it possible to store different kinds of objects in a single variable, opening the door to dynamic programming. For example, id lets you store an NSNumber, an NSDecimalNumber, or an NSString in the same variable:

id mysteryObject = [NSNumber numberWithInt:5];
NSLog(@"%@", mysteryObject);

mysteryObject = [NSDecimalNumber decimalNumberWithString:@"5.1"];
NSLog(@"%@", mysteryObject);

mysteryObject = @"5.2";
NSLog(@"%@", mysteryObject);

Note that id implies that the value will be a pointer, so variable declarations don't require an asterisk before the variable name. In other words, variables should always be declared as id mysteryObject, not id *mysteryObject.

Since an id variable doesn't check what kind of object it contains, it's the programmer's responsibility to makes sure he or she doesn't call methods or access properties that aren't defined on the object (e.g., don't try to call stringValue when the variable contains an NSString instance.

The Class Data Type

Objective-C classes are actually objects themselves, and they can be stored in variables using the Class type. You can get the class object associated with a particular class by sending it the class message. The following example shows how to retrieve a class object, store it in a Class variable, and use it to figure out which kind of object is stored in an id variable:

Class targetClass = [NSString class];

id mysteryObject = [NSNumber numberWithInt:5];
NSLog(@"%i", [mysteryObject isKindOfClass:targetClass]);

mysteryObject = [NSDecimalNumber decimalNumberWithString:@"5.1"];
NSLog(@"%i", [mysteryObject isKindOfClass:targetClass]);

mysteryObject = @"5.2";
NSLog(@"%i", [mysteryObject isKindOfClass:targetClass]);

The Class data type brings the same dynamic capabilities to classes that id brings to objects.

Foundation Data Structures Summary

The classes presented in the latter half of this chapter provide the foundation to nearly every Objective-C program. Strings, arrays, sets, and dictionaries are the core of nearly every programming language, and having such a high-level interface for representing data is an important aspect of productivity. We also saw how Objective-C needs to box C primitives for use with these Foundation framework classes, which provides a convenient API at the expense of performance and memory. Of course, you're always free to work with primitive data types in an Objective-C program.

We also examined two more object-oriented data types available to Objective-C applications: id and Class. Together, these open up a wide variety of possibilities for organizing an application.

This lesson represents a chapter from Objective-C Succinctly, a free eBook from the team at Syncfusion.
Advertisement