Safari Books Online is a digital library providing on-demand subscription access to thousands of learning resources.
Cocoa provides several classes in the Foundation Kit whose purpose is to hold and organize instances of other classes. These are called the collection classes. There are three primary flavors of collections in Cocoa: arrays , sets , and dictionaries . These classes, shown in Figure 4-3, are extremely useful in Cocoa application development, and their influence can be found throughout the Cocoa class libraries.
Collection classes, like strings, come in two forms: mutable and immutable . Immutable classes allow you to add items when the collection is created, but no further changes are allowed. On the other hand, mutable classes allow you to add and remove objects programmatically after the collection is created.
Much of the power of collection classes comes from their ability to manipulate the objects they contain. Not every collection object can perform every function, but in general, collection objects can do the following:
Derive their initial contents from files and URLs, as well as other collections of objects
Add, remove, locate, and sort contents
Compare their contents with other collection objects
Enumerate over their contents
Send a message to the objects that they contain
Archive their contents to a file on disk and retrieve it later[1]
[1] Objects placed into an array must implement certain methods to support this functionality. All of the Foundation classes that you are likely to add to a collection are already prepared for this.
Arrays—instances of the NSArray class—are ordered collections of objects indexed by integers. Like C-based arrays, the first object in an array is located at index 0. Unlike C- and Java-based arrays whose size is set when they are created, Cocoa mutable array objects can grow as needed to accommodate inserted objects.
The NSArray class provides the following methods to work with the contents of an array:
Returns the number of objects currently in the array.
Returns the object located in the array at the index given. Like C- and Java-based arrays, Cocoa array indexes start at 0.
Indicates whether a given object is present in the array.
To practice working with arrays do as follows:
In Project Builder, create a new Foundation Tool (File → New Project → Tool → Foundation Tool) named "arrays", and save it in your ~/LearningCocoa folder.
Open the main.m file, located in the "Source" group, and modify it to match the following code:
int main (int argc, const char * argv[]) {
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
NSString * string = @"one two buckle my shoe"; // a
NSArray * array = [string componentsSeparatedByString:@" "]; // b
int count = [array count]; // c
int i;
for ( i = 0; i < count; i++ ) {
printf("%i: %s\n", i, [[array objectAtIndex:i] UTF8String]); // d
}
[pool release];
return 0;
}
The code we added performs the following tasks:
Declares a new string.
Creates an array of string objects using the componentsSeparatedByString: method of the NSString class. Note that in the first example of this chapter, where we looked for the range of the comma to split the spring, we could have used this method to get the two strings.
Obtains the count of the array to use in the for loop.
Prints each item of the array to the console.
We'll explore a few more NSArray methods using the debugger:
Set a breakpoint after the for loop. If you typed in the code exactly as noted previously, including the spaces and the comments that are part of the main.m file template, the breakpoint will be on line 15.
Click on the Console tab to open up the debugger console.
Type in the following at the (gdb) prompt:
(gdb) print-object [array objectAtIndex:4]
You should see the following output:
shoe
Type in the following:
(gdb) print (int) [array containsObject:@"buckle"];
You should see the following output:
$1 = 1
This indicates that the array did contain the string we specified. Try using a string that isn't in the array, and see what the return value is. You should see a return value of 0.
Quit the debugger, and close the project.
The NSMutableArray class provides the functionality needed to manage a modifiable array of objects. This class extends the NSArray class by adding insertion and deletion operations. These operations include the following methods:
Inserts the given object to the end of the receiving array.
Inserts the given object to the receiving array at the index specified. All objects beyond the index are shifted down one slot to make room.
Removes the object from the receiving array located at the index and shifts all of the objects beyond the index up one slot to fill the gap.
Removes all occurrences of an object in the receiving array. The gaps left by the objects are removed by shifting the remaining objects.
The following steps will explore these methods:
In Project Builder, create a new Foundation Tool (File → New Project → Tool → Foundation Tool) named "mutablearrays", and save it in your ~/LearningCocoa folder.
Open the main.m file, located in the "Source" group, and modify it to match the code shown in Example 4-4.
|
Code View:
Scroll
/
Show All int main (int argc, const char * argv[]) {
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
NSMutableArray * array = [[NSMutableArray alloc] init]; // a
[array addObject:@"sheryl crow"]; // b
[array addObject:@"just wants to have fun"]; // c
printf("%s\n", [[array description] UTF8String]); // d
[array release]; // e
[pool release];
return 0;
}
|
The code we added in Example 4-4 performs the following tasks:
Creates a new mutable array
Adds an object to the array
Adds another object to the array
Prints the array
Releases the array, since we created it using the alloc method
We'll further explore the NSMutableArray class using the debugger:
Set a breakpoint before the line of code that releases the array (line 10).
Click on the Console tab to open up the debugger console.
First, we insert an object into the array after the first object. Then we'll print it out to see the modified array. Type in the following:
(gdb) call (void) [array insertObject:@"santa monica" atIndex:1] (gdb) print-object array
The following output should appear.
<NSCFArray 0x94be0>( sheryl crow, santa monica, just wants to have fun )
Now remove one of the objects:
(gdb) call (void) [array removeObject:@"just wants to have fun"] (gdb) print-object array
The following will be output:
<NSCFArray 0x94be0>( sheryl crow, santa monica )
As a quick example of how to use arrays in a situation that isn't so contrived, we will use an API introduced in Mac OS X 10.2—the Address Book API. The Address Book serves as a central contact database that can be used by all applications on the system. The hope is that you won't need a separate contact database for your mailer, for your fax software, etc. Already, the applications that ship with Mac OS X, such as Mail and iChat, utilize the Address Book. The Address Book application is shown in Figure 4-4.
Use the following steps to guide you in this exploration:
Launch the Address Book application (it is installed in your Dock by default; you can find it in the /Applications folder otherwise), and make sure that you have some contacts defined.
In Project Builder, create a new Foundation Tool (File → New Project → Tool → Foundation Tool) named "addresses", and save it to your ~/LearningCocoa folder.
Add the Address Book framework to the project by selecting the Project → Add Frameworks menu item. A dialog box will open, asking you to select the framework to add. It should open up to the /System/Library/Frameworks folder. If not, navigate to that folder, and select the AddressBook.framework folder to add to the project. After you click the Add button, a sheet will appear to control how the framework should be added. The settings shown will be fine, and all you need to do is click the Add button again.
This step ensures that Project Builder links against the AddressBook framework, as well as the Foundation framework, when it builds our application.
Open the main.m file, and modify it to match the following code:
#import <Foundation/Foundation.h> #import <AddressBook/AddressBook.h> // a int main (int argc, const char * argv[]) { NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; ABAddressBook * book = [ABAddressBook sharedAddressBook]; // b NSArray * people = [book people]; // c int count = [people count]; int i; for (i = 0; i < count; i++) { ABPerson * person = [people objectAtIndex:i]; // d NSString * firstName = [person valueForProperty:@"First"]; // e NSString * lastName = [person valueForProperty:@"Last"]; // f printf("%s %s\n", [lastName UTF8String], [firstName UTF8String]); // g } [pool release]; return 0; }
The code we added performs the following tasks:
Imports the AddressBook API set. Without this line, the compiler cannot compile the main.m file, because it won't be able to find the definitions for the Address Book classes.
Obtains the Address Book for the logged-in user.
Obtains an array containing all of the people in the Address Book.
Loops through the people to obtain an ABPerson object. The ABPerson class provides the methods to work with the various attributes that a person record has in the Address Book database.
Gets the first name of the person.
Gets the last name of the person.
Prints the name of the person out to the console.
|
Build and run (
Davidson James Duncan Hunter Jason Ronconi Eleo Horwat Justyna Driscoll Jim Davidson Ted Branham Christine Behlendorf Brian O'Reilly Tim Toporek Chuck Czigany Susan
We haven't gone into great detail on the use of the AddressBook, but just a little knowledge on arrays has already let you work with this important user data. By the time you're done with this book, just think how dangerous you will be! But no matter how dangerous you get, you should remember to use the Address Book API when you create an application that needs to keep track of contacts. Also, you'll be able to build some pretty neat apps using this data. For example, I'm considering building an application that automatically prints Christmas cards to send to all the contacts that I consider to be friends.
Sets—implemented by the NSSet and NSMutableSet classes—are an unordered collection of objects in which each object can appear only once. A set can be used instead of an array when the order of elements in the collection is not important, but when testing to see if an object is part of the set (usually referred to as "testing for membership"), speed is important. Testing to see if an object is a member of a set is faster than testing against an array.
Dictionaries—implemented in the NSDictionary class—store and retrieve objects using key-value pairs. Each key-value pair in a dictionary is called an entry . The keys in a dictionary form a set; a key can be used only once in a dictionary. Although the key is usually a string (an NSString object), most objects can be used as keys.[2] To enable the retrieval of a value at a later time, the key of the key-value pair should be immutable or treated as immutable. If the key changes after being used to put a value in the dictionary, the value might not be retrievable. The NSDictionary class provides the following methods to work with the contents of an array:
[2] The object used as a key must respond to the isEqual: message and conform to the NSCopying protocol. Since we have not covered protocols yet, the rule of thumb is that any Cocoa object provided in the Foundation framework can be used as a key. Other objects may not work.
Returns the number of objects currently in the dictionary
Returns the object that is indexed using the given key in the dictionary
Returns an array containing all of the keys in the dictionary
To practice working with dictionaries:
In Project Builder, create a new Foundation Tool (File → New Project → Tool → Foundation Tool) named "dictionaries", and save it in your ~/LearningCocoa folder.
Open the main.m file, located in the "Source" group, and modify it to match the code shown in Example 4-5.
|
Code View:
Scroll
/
Show All int main (int argc, const char * argv[]) {
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
NSArray * keys =
[@"one two three four five" componentsSeparatedByString:@" "]; // a
NSArray * values =
[@"alpha bravo charlie delta echo" componentsSeparatedByString:@" "]; // b
NSDictionary * dict = [[NSDictionary alloc] initWithObjects:values
forKeys:keys]; // c
printf("%s\n", [[dict description] UTF8String]); // d
[pool release];
return 0;
}
|
The code we added in Example 4-5 performs the following tasks:
Creates a new array based on a space-delimited string. This set of objects will serve as the keys for the dictionary.
Creates a new array that will serve as the values of the dictionary.
Creates a new dictionary with our keys and values.
Prints the dictionary, so it can be examined.
This is a representation of the structure of the dictionary. Note that the elements are not stored in any particular order. Remember that the keys form a set in which uniqueness, not order, is critical.
We'll explore this example further using the debugger.
Set a breakpoint after the printf statement. If you typed in the code exactly as listed earlier, the breakpoint will be on line 15.
Type the following:
(gdb) print-object [dict objectForKey:@"three"]
The following will be output:
charlie
Type the following:
(gdb) print-object [dict allKeys]
The following will be output:
<NSCFArray 0x97800>( two, four, three, one, five )
Quit the debugger, and close the project.
The strengths of the dictionary classes will become apparent when we discuss how they can hold and organize data that can be labeled, such as values extracted from text fields in a user interface. We'll show this in action in Chapter 9, when we show how you can work with dictionaries to drive tables in user interfaces.
The NSMutableDictionary class provides the functionality needed to manage a modifiable dictionary. This class extends the NSDictionary class by adding insertion and deletion operations. These operations include the following methods:
Adds an entry to the dictionary, consisting of the given key-value pair. If the key already exists in the dictionary, the previous object associated with that key is removed from the dictionary and replaced with the new object.
Removes the key and its associated value from the dictionary.
One of the nicer things about Cocoa's collection classes is that they support the writing and reading of collection data to and from files called property lists , or plist files. This lets you store your data easily and read it later. In fact, Mac OS X uses property lists extensively to store all kinds of data, such as user preferences, application settings, and system-configuration data. In upcoming chapters, we'll be working with user preferences (also known as defaults) and we will see how Mac OS X uses plists in application bundles.
The methods to support this functionality are relatively simple. For the array and dictionary classes, these methods are as follows:
Initializes a newly allocated array or dictionary with the contents of the file specified by the path argument
Writes the contents of an array or dictionary to the file specified by the path argument
To practice working with collections and files do as follows:
In Project Builder, create a new Foundation Tool (File → New Project → Tool → Foundation Tool) named "collectionfiles", and save it in your ~/LearningCocoa folder.
Open the main.m file, and modify it to match Example 4-6.
|
Code View:
Scroll
/
Show All #import <Foundation/Foundation.h>
int main (int argc, const char * argv[]) {
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
NSMutableArray * array = [[NSMutableArray alloc] init]; // a
[array addObject:@"San Francisco"]; // b
[array addObject:@"Houston"];
[array addObject:@"Tulsa"];
[array addObject:@"Juneau"];
[array addObject:@"Pheonix"];
[array writeToFile:@"cities.plist" atomically:YES]; // c
NSString * plist =
[NSString stringWithContentsOfFile:@"cities.plist"]; // d
printf("%s\n", [plist UTF8String]); // e
[array release]; // f
[pool release];
return 0;
}
|
The code we added inExample 4-6 does the following things:
Creates a new mutable array.
Adds a series of strings to the mutable array.
Writes the array to a file named cities.plist. Since this is not an absolute path, it will be written in the working directory of application. In our case, this file will be written in ~/LearningCocoa/collectionfiles/build/cities.plist.
Creates a new string based on the contents of the file that we just wrote. Once again, we use a relative path.
Prints the contents of the file to the console.
Returns the array object that we created.
Build and run (
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.
com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<array>
<string>San Francisco</string>
<string>Houston</string>
<string>Tulsa</string>
<string>Juneau</string>
<string>Pheonix</string>
</array>
</plist>
This is an XML representation of the array. This data can be edited with a text editor, transmitted across the Internet, or turned back into a collection of strings in another Cocoa program.