Free Trial

Safari Books Online is a digital library providing on-demand subscription access to thousands of learning resources.


  • Create BookmarkCreate Bookmark
  • Create Note or TagCreate Note or Tag
  • DownloadDownload
  • PrintPrint

4.2. Collections

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.

Figure 4-3. Cocoa collection classes


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.

4.2.1. Arrays

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:


- (unsigned)

count

Returns the number of objects currently in the array.


- (id)

objectAtIndex:
(unsigned)index

Returns the object located in the array at the index given. Like C- and Java-based arrays, Cocoa array indexes start at 0.


- (BOOL)

containsObject:
(id)anObject

Indicates whether a given object is present in the array.

To practice working with arrays do as follows:

  1. In Project Builder, create a new Foundation Tool (File New Project Tool Foundation Tool) named "arrays", and save it in your ~/LearningCocoa folder.

  2. 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:

    1. Declares a new string.

    2. 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.

    3. Obtains the count of the array to use in the for loop.

    4. Prints each item of the array to the console.

  3. Build and run (


    -R) the application. You should see output similar to the following appear in the console:

    0: one
    1: two
    2: buckle
    3: my
    4: shoe

4.2.1.1. Using the debugger to explore NSArray

We'll explore a few more NSArray methods using the debugger:

  1. 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.

  2. Build and debug (


    -Y) the application. Execution will start and then pause at the breakpoint we set.

  3. Click on the Console tab to open up the debugger console.

  4. Type in the following at the (gdb) prompt:

    (gdb) print-object [array objectAtIndex:4]
    								

    You should see the following output:

    shoe

  5. 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.

  6. Quit the debugger, and close the project.

4.2.2. Mutable Arrays

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:


- (void)

addObject:
(id)anObject

Inserts the given object to the end of the receiving array.


- (void)

insertObject:
(id)anObject
atIndex:
(unsigned index)

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.


- (void)

removeObjectAtIndex:
(unsigned index)

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.


- (void)

removeObject:
(id)anObject

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:

  1. In Project Builder, create a new Foundation Tool (File New Project Tool Foundation Tool) named "mutablearrays", and save it in your ~/LearningCocoa folder.

  2. Open the main.m file, located in the "Source" group, and modify it to match the code shown in Example 4-4.

    Example 4-4. Working with mutable arrays

    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:

    1. Creates a new mutable array

    2. Adds an object to the array

    3. Adds another object to the array

    4. Prints the array

    5. Releases the array, since we created it using the alloc method

  3. Build and run (


    -R) the application. You should see the following output in the console:

    ("sheryl crow", "just wants to have fun")

4.2.2.1. Exploring NSMutableArray with the debugger

We'll further explore the NSMutableArray class using the debugger:

  1. Set a breakpoint before the line of code that releases the array (line 10).

  2. Build and debug (


    -Y) the application. Execution will start and then pause at the breakpoint.

  3. Click on the Console tab to open up the debugger console.

  4. 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
    )

  5. 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
    )

  6. Quit the debugger, and close the project.

4.2.3. Arrays and the Address Book

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.

Figure 4-4. The Address Book


Use the following steps to guide you in this exploration:

  1. 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.

  2. In Project Builder, create a new Foundation Tool (File New Project Tool Foundation Tool) named "addresses", and save it to your ~/LearningCocoa folder.

  3. 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.

  4. 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:

    1. 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.

    2. Obtains the Address Book for the logged-in user.

    3. Obtains an array containing all of the people in the Address Book.

    4. 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.

    5. Gets the first name of the person.

    6. Gets the last name of the person.

    7. Prints the name of the person out to the console.

    For more information about the various classes in the AddressBook framework, see the files in /Developer/Documentation/AdditionalTechnologies/AddressBook.


  5. Build and run (


    -R) the application. You should see a list of your contacts output in the console. Here's a sample from our run of the application, using the contacts pictured in Figure 4-4:

    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.

4.2.4. Sets

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.

4.2.5. Dictionaries

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.


- (unsigned)

count

Returns the number of objects currently in the dictionary


- (id)

objectForKey:
(id)aKey

Returns the object that is indexed using the given key in the dictionary


- (NSArray *)

allKeys

Returns an array containing all of the keys in the dictionary

To practice working with dictionaries:

  1. In Project Builder, create a new Foundation Tool (File New Project Tool Foundation Tool) named "dictionaries", and save it in your ~/LearningCocoa folder.

  2. Open the main.m file, located in the "Source" group, and modify it to match the code shown in Example 4-5.

    Example 4-5. Working with dictionaries

    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:

    1. Creates a new array based on a space-delimited string. This set of objects will serve as the keys for the dictionary.

    2. Creates a new array that will serve as the values of the dictionary.

    3. Creates a new dictionary with our keys and values.

    4. Prints the dictionary, so it can be examined.

  3. Build and run (


    -R) the application. You should see output similar to the following appear in the console:

    {five = echo; four = delta; one = alpha; three = charlie; two = bravo; }

  4. 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.

  1. Set a breakpoint after the printf statement. If you typed in the code exactly as listed earlier, the breakpoint will be on line 15.

  2. Build and debug (


    -Y) the application, open the debugger console, and type the following:

    (gdb) print (int) [dict count]
    							

    The following will be output:

    $1 = 5

    This tells us that there are five elements in the collection.

  3. Type the following:

    (gdb) print-object [dict objectForKey:@"three"]
    							

    The following will be output:

    charlie 

  4. Type the following:

    (gdb) print-object [dict allKeys]
    							

    The following will be output:

    <NSCFArray 0x97800>(
    two,
    four,
    three,
    one,
    five
    )

  5. 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.

4.2.6. Mutable Dictionaries

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:


- (void)

setObject:
(id)anObject
forKey:
(id)aKey

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.


- (void)removeObjectForKey:

(id)aKey 

Removes the key and its associated value from the dictionary.

Working with Numbers

Collections can hold only objects; they cannot hold C-based primitive types such as int, float, and long. However, there will be many cases where you will want to store primitive types in your collections. To allow these types to be manipulated as objects, Cocoa provides the NSNumber class. This class can wrap any C numeric type and defines a group of methods to set and access the value. In addition, it defines a compare: method, allowing two numbers to be compared with each other.


4.2.7. Storing Collections as Files

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:


- (id)

initWithContentsOfFile:
(NSString *)aPath

Initializes a newly allocated array or dictionary with the contents of the file specified by the path argument


- (BOOL)

writeToFile:
(NSString *)path
atomically:
(BOOL)flag

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:

  1. In Project Builder, create a new Foundation Tool (File New Project Tool Foundation Tool) named "collectionfiles", and save it in your ~/LearningCocoa folder.

  2. Open the main.m file, and modify it to match Example 4-6.

    Example 4-6. Working with property lists

    #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:

    1. Creates a new mutable array.

    2. Adds a series of strings to the mutable array.

    3. 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.

    4. Creates a new string based on the contents of the file that we just wrote. Once again, we use a relative path.

    5. Prints the contents of the file to the console.

    6. Returns the array object that we created.

  3. Build and run (


    -R) the application. You should see output similar to the following appear in the console:

    <?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.

Property Lists

Mac OS X uses property lists, frequently referred to as plists, to organize data into a form that is meaningfully structured, easily transportable, and storable. Property list files are saved in an XML format for easy editing and transportability.

You can see many examples of plist files in your ~/Library/Preferences folder. Property lists organize data into named values and lists of values using several types directly represented as the following Cocoa objects: NSString, NSNumber, Boolean, NSDate, NSData, NSArray, and NSDictionary. They make it easy for applications to store preference data and, the case of the Info.plist file in an application bundle, communicate information about an application to the system. To take a look at how applications can use plists to store configuration information, look at the plist file for the menu bar clock.

  1. Open the ~/Library/Preferences folder, and locate the com.apple.MenuBarClock.plist file.

  2. Double-click on the file to open it with the Property List Editor application (located in the /Developer/Applications folder).

The Property List Editor application can be used to browse the tree of properties. You can also hit the Dump button to see the XML representation of the property list. Be careful not to save any edits you make, as you can severely confuse an application by making the wrong changes here.