Learning Cocoa with Objective-C/Cocoa Overview and Foundation/The Cocoa Foundation Kit

From WikiContent

< Learning Cocoa with Objective-C | Cocoa Overview and Foundation
Revision as of 12:56, 7 March 2008 by Docbook2Wiki (Talk)
(diff) ←Older revision | Current revision (diff) | Newer revision→ (diff)
Jump to: navigation, search
Learning Cocoa with Objective-C

Now that we have filled your head with lots of theory about object-oriented programming, we'll look into some of the essential parts of Cocoa's Foundation framework. In this chapter we cover strings, collections, and memory management. Once you have a firm grasp on these topics, you'll be ready for the raison d'être of Cocoa: GUI programming.

The nature of these topics doesn't lend itself to a nifty, all-inclusive code example that shows everything in action at once. So, instead of contriving a single, awkward example, we're just going to work through a set of simple code samples to illustrate the concepts presented. We'll also augment the use of these samples with some usage of the debugger.

Contents

Strings

So far, we have worked with strings using the @" . . . " construct in various method and function calls. This construct is convenient when working with strings. When interpreted by the compiler, it is translated into an NSString object that is based on the 7-bit ASCII-encoded string (also known as a "C string") between the quotes. For example, the statement:

NSString * lush = @"Lush";

is functionally equivalent to:

NSString * lush = [[NSString alloc] initWithCString:"Lush"];

NSString objects are not limited to the ASCII character set; they can handle any character contained in the Unicode character set, allowing most of the world's living languages to be represented. Unicode is a 16-bit-wide character set, but can be represented in 8-bits using the UTF-8 encoding.

Basic String Operations

NSString provides several methods that are handy when working with strings. A few of these methods are as follows:

- (int)length
Returns the number of Unicode characters in the string object upon which it is called.

- (const char *)cString
Returns a representation of the string as a C string in the default encoding. This method is helpful when you need to operate with C-based functions, such as those found in traditional Unix system calls.

Warning

It's important to note that since C strings are 7-bit, and the NSString class can handle the full Unicode character set, not all NSString objects can be represented as C strings.

- (const char *) UTF8String
Returns a representation of the string as a UTF-8 representation. UTF-8 allows the transmission of Unicode characters over channels that support 8-bit encodings. All of the lower levels of Mac OS X — including the HFS+ and UFS filesystems, as well as the BSD system routines — can handle char * arguments in the UTF-8 encoding.
- (NSString *)stringByAppendingString:(NSString *)aString
Returns a new string object by appending the given string to the string upon which the method is called.

To explore these methods, we'll create a simple program using the following steps:

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

    Tip

    You may have noticed that sometimes our project names start with a lowercase letter and sometimes with an uppercase letter. The common practice in naming applications is that command-line applications should be lowercase and GUI applications should be initial capitalized. We'll use this practice through this book.

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

    Example 4-1. Working with strings

    int main (int argc, const char * argv[]) {
        NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
    
        NSString * artist = @"Underworld";                               // a
    
                                   NSLog(@"%@ has length: %d", artist, [artist length])             // b
    
    
        [pool release];
        return 0;
    }
    

    The code we added in Example 4-1 performs the following tasks:

    1. Declares an object of type NSString, named artist, and sets it to the value "Underworld"
    2. Obtains the length of the string and prints it using the NSLog function
  3. Build and run ([[Image:Learning Cocoa with Objective-C_I_4_tt84.png|]]-R) the application. You will be prompted to save the main.mfile, and then Project Builder will compile and the run the code. You should see the following output in Project Builder's console:
    2002-06-17 23:29:32.344 strings[1147] Underworld has length: 10
    

    As we have seen before, the NSLog function prints the current date and time, the program name, and the process ID (PID) of the program, as well as the output that we told it to print. In the output, we see that the artist object was substituted for the %@ token and that the return value from the length method was substituted for the %d token. Remember, you can use any of the standard printf substitution tokens in the format string, in addition to the %@ token.


Setting breakpoints and debugging

Instead of adding code to the strings tool, we will use the debugger to explore the UTF8String and stringByAppendingString methods. This will give you some practice using the debugger, while you learn about these methods.

  1. Set a breakpoint between the NSLog function and the [pool release] line of code in main.m. Remember to set a breakpoint, click in the column on the left side of the code editor. If you want to move the breakpoint, click and drag the breakpoint to its new location. In our code, this breakpoint is at line 8. An example of the breakpoint set is shown in Figure 4-1.

    Figure 4-1. Setting a breakpoint in the main.m file

    Setting a breakpoint in the main.m file

  2. Build and debug the application (Build → Build and Debug, or [[Image:Learning Cocoa with Objective-C_I_4_tt87.png|]]-Y). Execution will start and then pause at the breakpoint we set, highlighting the line at which it stopped in a salmon colored bar.
  3. Click on the Console tab above the variable viewer to open up the debugger console, as shown in Figure 4-2. You should see that the NSLog function outputs its string.

    Figure 4-2. Program execution paused at breakpoint

    Program execution paused at breakpoint

    Tip

    The debugger console behaves similarly to working with the Terminal application. You enter a command, hit Return, and the result of the command is shown on the next line. Just like the default shell in the Terminal, the debugger maintains a history of commands that you can access by hitting the up and down arrows on your keyboard.

  4. Type in print-object artist at the (gdb) prompt in the debugger console. You may have to click in the debugger console to give it focus, so that you can enter commands.
    (gdb) print-object artist
                            
    

    When you enter this command, the debugger outputs the following:

    Underworld
    

In addition to simply printing objects, we can print the result of any message that we can send to an object. This functionality is incredibly useful when trying to find the various states of an object while using the debugger.

  1. Enter in the following into the debugger console:
    (gdb) print-object [artist description]
                            
    

    The following result, matching what we just saw in Step 4, will be printed:

    Underworld
    
  2. Let's see the stringByAppendingString method in action. Enter the following into the debugger console:
    (gdb) print-object [artist stringByAppendingString:@": Pearl's Girl"]
                            
    

    The debugger outputs the following result of the method call:

    Underworld: Pearl's Girl
    
  3. You can also send messages to NSString objects created using the @"..." construct. Enter the following into the debugger console:
    (gdb) print-object [@"The artist is: " stringByAppendingString:artist]
                            
    

    The debugger outputs:

    The artist is: Underworld
    

The next debugger command we will learn is the print command. This command prints out C types instead of objects. We will use the print command to evaluate the return values of the length and UTF8String methods.

  1. Enter the following into the debugger console:
    (gdb) print (int) [artist length]
                            
    

    The debugger outputs:

    $1 = 10
    

    The $1 symbol is a temporary variable that holds the results of the message, and the 10 denotes the number of characters (or length) in the artist object. Note that we needed to cast the return type from the length message so that the print command could operate. Try this again without the (int) cast.

  2. To see the UTF8String method in action, enter the following:
    (gdb) print (char *) [artist UTF8String]
                            
    

    The debugger outputs something similar to the following:

    $2 = 0x9f738 "Underworld\000"...
    

    This is the null-terminated char * string representation, in UTF-8 encoding, of our artist string.

    To quit the debugger, you can either click the stop button or enter quit at the (gdb) prompt.


Working with Portions of a String

When working with strings, it often is necessary to extract data from them. The NSString class provides the following methods for finding and obtaining substrings:

- (NSRange)rangeOfString:(NSString *)aString
Returns an NSRange struct that contains the location and length of the first occurrence of the given string
- (NSString *)substringFromIndex:(unsigned)index
Returns a string object that contains the characters of the receiver, from the index given to the end of the string
- (NSString *)substringToIndex:(unsigned)index
Returns a string object that contains the characters of the receiver, from the beginning of the string to the index given
- (NSString *)substringWithRange:(NSRange)range
Returns a string object that contains the characters of the receiver, within the range specified

To explore these methods, we'll create a simple program (that works with just substrings) using the following steps:

  1. In Project Builder, create a new Foundation Tool (File → New Project → Tool → Foundation Tool) named "substrings", and save it in your ~/LearningCocoafolder.
  2. Open the main.mfile, located in the "Source" group, and modify it to match the code shown in Example 4-2.

    Example 4-2. Working with substrings

    int main (int argc, const char * argv[]) {
        NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
    
        NSString * song = @"Let Forever Be,The Chemical Brothers";          // a
                                   NSRange range = [song rangeOfString:@ ","];                         // b
                                   printf("comma location: %i\n", range.location);                     // c
    
                                   NSString * title = [song substringToIndex:range.location];          // d
                                   NSString * artist = 
                                       [song substringFromIndex:range.location + range.length];        // e
    
                                   printf("title:  %s\n", [title UTF8String]);                         // f
                                   printf("artist: %s\n", [artist UTF8String]);                        // g
    
        [pool release];
        return 0;
    }
    

    The code we added in Example 4-2 performs the following tasks:

    1. Declares a string object named song and sets it.
    2. Obtains the range of the comma in the song string.
    3. Prints the location of the comma. Notice that we are using the standard C printf function here. We will use printf instead of NSLog in many of the upcoming exercises, so the output from our programs won't be cluttered with timestamps and PIDs. Note that, unlike the NSLog function, we have to be sure to include the \n character to print out the new line.
    4. Declares a string named title and sets it to the substring, from the start of the song string to the location of the comma.
    5. Declares a string named artist and sets it to the substring, from the comma to the end of the song string. We use the range.location + range.length construction so that we find the index just after the comma value. If we just used the location of the comma, it would show up in our substring.
    6. Prints the title to the console, using the UTF-8 representation of the string. Notice that we are using the printf %s token.
    7. Prints the artist to the console, using the UTF-8 representation of the string.
  3. Build and run ([[Image:Learning Cocoa with Objective-C_I_4_tt101.png|]]-R) the application. You will be prompted to save your files, and then Project Builder will compile and run the code. You should see the following output in the console:
    comma location: 14
    title:  Let Forever Be
    artist: The Chemical Brothers
    

Mutable Strings

Once created, instances of the NSString class cannot be changed; they are immutable. If you want to change the contents of an NSString object, you must create a new one, as we saw using the stringByAppendingString method. In programs that manipulate strings extensively, this would become cumbersome quickly. To let you modify the contents of a string, Cocoa provides the NSMutableString class.

Tip

If you have programmed in Java, NSMutableString can be considered analogous to the java.lang.StringBuffer class.

Some of the methods that you frequently will use with mutable strings are the following:

- (void)appendString:(NSString *)aString
Adds the characters of the given string to those already in the mutable string object upon which the method is called.
- (void)deleteCharactersInRange:(NSRange)range
Deletes the characters in a given range.
- (void) insertString: (NSString *)aString atIndex:(unsigned index)
Inserts the characters of the given string into the mutable string at the location specified by the index. All of the characters from the insertion point to the end of the mutable string are shifted to accommodate the new characters.

To explore these methods, we'll create yet another simple program, using the following steps:

  1. In Project Builder, create a new Foundation Tool (File → New Project → Tool → Foundation Tool) named "mutablestrings", and save it in your ~/LearningCocoafolder.
  2. Open the main.mfile, 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];
        NSMutableString * song = [[NSMutableString alloc] init];           // a
                                [song appendString:@"Deaf Leppard"];                               // b
                                printf("%s\n", [song UTF8String]);                                 // c
    
                                NSRange range = [song rangeOfString:@"Deaf"];                      // d
                                [song replaceCharactersInRange:range withString:@"Def"];           // e
                                printf("%s\n", [song UTF8String]);                                 // f
    
                                [song insertString:@"Animal by " atIndex:0];                       // g
                                printf("%s\n", [song UTF8String]);                                 // h
    
                                [song release];                                                    // i
    
        [pool release];
        return 0;
    }
    

    The code we added performs the following tasks:

    1. Creates a new empty mutable string named song.
    2. Appends the contents of the "Deaf Leppard" string to the song mutable string.
    3. Prints the song mutable string to the console.
    4. Gets the range of the "Deaf" substring.
    5. Replaces the "Deaf" substring with "Def" to correct the misspelling.
    6. Prints the song mutable string to the console.
    7. Inserts the string "Animal by" at the beginning the mutable string.
    8. Once again prints the song mutable string.
    9. Releases the song object. Because we created the Song object using the alloc method, we are responsible forr releasing it. We'll explain more about how this works later in this chapter.
  3. Build and run ([[Image:Learning Cocoa with Objective-C_I_4_tt104.png|]]-R) the application. You should see the following output in the console:
    Deaf Leppard
    Def Leppard
    Animal by Def Leppard
    

Working with Files

A common use of strings is to work with paths to files in the filesystem. The NSString class provides several methods to manipulate strings as filesystem paths, extract a file name or an extension, resolve paths containing symbolic links, and even expand tilde expressions (such as ~duncan/Library) in paths. Some of the commonly used path manipulation methods are as follows:

- (NSString *)lastPathComponent
Returns the last path component of the receiver. For example, if you call this method on the string ~/LearningCocoa/substrings/main.m, it will return main.m.
- (NSString *)pathExtension
Returns the extension, if any, of a file path. For example, if you call this method on the string main.m, it will return the value m.
- (NSString *)stringByStandardizingPath
Returns a string with all extraneous path components removed or resolved. This method will resolve the initial tilde expression, as well as any .. or ./ symbols, to actual directories.

In addition to working with paths, you can also create string objects using the contents of a file and write string objects to files using the following methods:

- (NSString *)stringWithContentsOfFile:(NSString *)path
Creates a new string by reading characters from the file specified by the path argument.
- (BOOL)writeToFile:(NSString *)path atomically:(BOOL)flag
Writes the contents of the string to the given file. The atomically flag indicates whether the file should be written safely to an auxiliary file, then copied into place. Most of the time, this setting makes no difference. The only time it matters is if the system crashes when the file is being flushed to disk.

To see these methods in action, follow the following steps:

  1. In Project Builder, create a new Foundation Tool (File → New Project → Tool → Foundation Tool) named "filestrings", and save it in your ~/LearningCocoafolder.
  2. Open the main.mfile, located in the "Source" group, and modify it to match the code shown in Example 4-3.

    Example 4-3. Reading files into strings

    int main (int argc, const char * argv[]) {
        NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
    
    
        NSString * filename = @"~/LearningCocoa/filestrings/main.m";        // a
    
                                   filename = [filename stringByStandardizingPath];                    // b
    
                                   printf("%s\n", [filename UTF8String]);                              // c
    
    
    
                                   NSString * source = [NSString stringWithContentsOfFile:filename];   // d
    
                                   printf("%s\n", [source UTF8String]);                                // e
    
    
        [pool release];
        return 0;
    }
    

    The code we added in Example 4-3 performs the following tasks:

    1. Creates a string object, named filename, that contains the path to the main.msource file of this project. Note that you must save your project in your ~/LearningCocoafolder for this example to work. If you are saving your projects to some other location, you will need to edit the path appropriately.
    2. Sets the filename variable to a standardized path. This will resolve the ~/ characters to your home directory.
    3. Prints the resolved filename variable.
    4. Creates a new string, named source, with the contents of the main.msource file.
    5. Prints the source string to the console.
  3. Build and run ([[Image:Learning Cocoa with Objective-C_I_4_tt106.png|]]-R) the application. You should see output similar to the following appear in the console:
    /Users/duncan/LearningCocoa/filestrings/main.m
    #import <Foundation/Foundation.h>
    
    int main (int argc, const char * argv[]) {
        NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
    
        NSString * filename = @"~/LearningCocoa/filestrings/main.m";
        filename = [filename stringByStandardizingPath];
        printf("%s\n", [filename UTF8String]);
    
        NSString * source = [NSString stringWithContentsOfFile:filename];
        printf("%s\n", [source UTF8String]);
    
        [pool release];
        return 0;
    }
    

We'll explore the lastPathComponent and pathExtension methods using the debugger.

  1. Set a breakpoint between the last printf statement and the [pool release]; line. If you typed the code exactly as shown in Example 4-3, the breakpoint will be on line 12.
  2. Build and debug the application ([[Image:Learning Cocoa with Objective-C_I_4_tt108.png|]]-Y). Execution will start and then pause at the breakpoint.
  3. Click on the Console tab above the variable viewer to open up the debugger console.
  4. Type in the following at the (gdb) prompt:
    (gdb) print-object [filename 
                            lastPathComponent]
                         
    

    When you enter this command, the debugger should output the following:

    main.m
    
  5. Type in the following at the (gdb) prompt:
    (gdb) print-object [filename 
                            pathExtension]
                         
    

    When you enter this command, you should see the following:

    m
    
  6. Quit the debugger; use the Stop button, or type in quit at the (gdb) prompt and hit return.

Now that we've covered quite a few things that you can do with strings, it's time to look at Cocoa's collection classes.

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

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]

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 ~/LearningCocoafolder.
  2. Open the main.mfile, 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 ([[Image:Learning Cocoa with Objective-C_I_4_tt115.png|]]-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
    

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 ([[Image:Learning Cocoa with Objective-C_I_4_tt117.png|]]-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.

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 ~/LearningCocoafolder.
  2. Open the main.mfile, 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 ([[Image:Learning Cocoa with Objective-C_I_4_tt122.png|]]-R) the application. You should see the following output in the console:
    ("sheryl crow", "just wants to have fun")
    

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 ([[Image:Learning Cocoa with Objective-C_I_4_tt124.png|]]-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.

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

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 /Applicationsfolderotherwise), 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 ~/LearningCocoafolder.
  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/Frameworksfolder. If not, navigate to that folder, and select the AddressBook.frameworkfolderto 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.mfile, 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.

    Tip

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

  5. Build and run ([[Image:Learning Cocoa with Objective-C_I_4_tt131.png|]]-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.

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.

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:

- (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 ~/LearningCocoafolder.
  2. Open the main.mfile, 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 ([[Image:Learning Cocoa with Objective-C_I_4_tt133.png|]]-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 ([[Image:Learning Cocoa with Objective-C_I_4_tt135.png|]]-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.

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. <tt/>

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 ~/LearningCocoafolder.
  2. Open the main.mfile, 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 ([[Image:Learning Cocoa with Objective-C_I_4_tt142.png|]]-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.


Memory Management

Memory management is an important subject in programming. Quite a few of the problems encountered by novice application developers are caused by poor memory management. When an object is created and passed around among various "consumer" objects in an application, which object is responsible for disposing of it and when? If an object is not deallocated when it is no longer needed, memory leaks. If the object is deallocated too soon, problems may occur in other objects that assume its existence, and the application will most likely crash.

The Foundation framework defines a mechanism and a policy that ensures that objects are deallocated only when they are no longer needed. We have hinted at it before, but now it is time to explain things.

The policy is quite simple: you are responsible for disposing of all objects that you own. You own objects that you create, either by allocating or copying them. You also own (or share ownership in) objects that you retain. The flip side of this rule is that you should never release an object that you have not retained or created; doing so will free the object prematurely, resulting in bugs that are hard to track down, even though the fix is simple.

Object Initialization and Deallocation

As discussed in Chapter 3, an object is usually created using the alloc method and is initialized using the init method (or a variant of the init method). When an array's init method is invoked, the method initializes the array's instance variables to default values and completes other startup tasks. For example:

NSArray * array = [[NSArray alloc] init];

When done with an object that you created, you send the release message to the object. If no other objects have registered an interest in the object, it will be deallocated and removed from memory.

When an object is deallocated, the dealloc method is invoked, giving the object an opportunity to release objects it has created, free allocated memory, and so on. We saw this in action in Chapter 3, when we added the dealloc method to the Song class.

Reference Counting

To allow multiple objects to register interest in another object and yet have this object removed from memory when no other objects are interested in it, each object in Cocoa has an associated reference count. When you allocate or copy an object, its reference count is automatically set to 1. This indicates that the object is in use in one place. When you pass the object to other objects, wanting to make sure the object stays around for their use, they can use the retain method to increment the reference counter.

To visualize this, imagine that we have an object being held in three different arrays, as shown in Figure 4-5. Each array retains the object to make sure that it remains available for its use. Therefore, the object has a reference count of 3.

Figure 4-5. Reference counting

Reference counting

Whenever you are done with an object, you send a release message to decrement the reference count. When the reference count reaches 0, the release method will invoke the object's dealloc method that destroys the object. Figure 4-6 shows an object being removed progressively from a set of arrays. When it is no longer needed, its retain count is set to 0, and the object is deallocated.

Figure 4-6. Releasing of an object

Releasing of an object

Autorelease Pools

According to the policy of disposing of all objects you create, if the owner of an object must release the object within its programmatic scope, how can the owner give that object to other objects? Or, said another way, how do you release an object you would like to return to the caller of a method? Once you return from a method, there's no way to go back and release the object.

The answer is provided by the autorelease method built into the NSObject class, in conjunction with the functionality of the NSAutoReleasePool class. The autorelease method marks the receiver for later release by an NSAutoreleasePool. This enables an object to live beyond the scope of the owning object so that other objects can use it. This mechanism explains why you have seen dozens of code examples that contain the following lines:

NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
...code...
[pool release]; 

Each application puts in place at least one autorelease pool (for each thread of control that is running in the application) and can have many more. You put an object in the pool by sending the object an autorelease message. In the case of an application's event cycle, when code finishes executing and control returns to the application object, the application object sends a release message to the autorelease pool, and the pool sends a release message to each object it contains. Any object that reaches a reference count of 0 automatically deallocates itself.

When an object is used solely within the scope of a method that creates it, you can deallocate it immediately by sending it a release message. Otherwise, use the autorelease message for all objects you create and hand off to other objects so that they can choose whether to retain them.

You shouldn't release objects that you receive from other objects, unless you have first retained them for some reason. Doing so will cause their reference count to reach 0 prematurely, and the system will destroy the object, thinking that no other object depends on it. When objects that do depend on the destroyed object try to access it, the application will most likely crash. These kinds of bugs can be hard to track down, even though their cause and fix are simple.

You can assume that a received object remains valid within the method in which it was received and will remain valid for the event loop that is handling it. If you want to keep it as an instance variable, you should send it a retain message and then autorelease it when you are done using it.

Retaining Objects in Accessor Methods

One of the primary places where you will need to be aware of memory management is in the accessor methods of your classes. At first glance, it is obvious that you will want to release an old object reference and retain the new one. However, because code that calls a class's setter method might call it multiple times with the same object as an argument, the order in which you release and retain the object references is important.

As a rule, you want to retain the new object before releasing the old one. This ensures that everything works as anticipated, even if the new and old objects are the same. If you reverse these steps, and if the new and old objects are actually the same, the object might be removed permanently from memory before being retained.

Here is the retain, then release rule expressed in code:

- (void)setProperty:(id)newProperty
{
    [newProperty retain];
    [property release];
    property = newProperty;
}

There are other ways to ensure connections in setter methods, many of which are valid and appropriate for certain situations. However, this is the simplest possible pattern we can give that will always work. We will use this pattern throughout the book.

Rules of Thumb

The important things to remember about memory management in Cocoa distill down to these rules of thumb:

  1. Objects created by alloc or copy have a retain count of 1.
  2. Assume that objects obtained by any other method have a retain count of 1 and reside in the autorelease pool. If you want to keep it beyond the current scope of execution, then you must retain it.
  3. When you add an object to a collection, it is retained. When you remove an object from a collection, it is released. Releasing a collection object (such as an NSArray) releases all objects stored in it as well.
  4. Make sure that there are as many release or autorelease messages sent to objects as there are alloc, copy, mutableCopy, or retain messages sent. In other words, make sure that the code you write is balanced.
  5. Retain, then release objects in setter methods.
  6. NSString objects created using the @" . . . " construct are effectively constants in the program. Sending retain or release messages to them has no effect. This explains why we haven't been releasing the strings created with the @" . . . " construct.

If you apply these rules of thumb consistently and keep the retain counts of your objects balanced, you can manage memory in your applications effectively.

Exercises

  1. Investigate the lowercase and uppercase methods of NSString using the debugger.
  2. Write a Foundation Tool command-line application that prints the contents of any filename given to it.
  3. Read the documentation on your hard drive about the NSArray, NSSet, and NSDictionary classes.
  4. Modify the arrays example application so that it saves the contents of the array to a file.
  5. Write an example that saves a dictionary to disk. Don't just use string objects in the array, but use some other objects like dictionaries and numbers so that you can see how Cocoa saves different types out to XML property lists.
  6. Examine the code we've written so far with an eye for how memory is managed. (A bug regarding memory management has been left in one of the examples.)

Notes

  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.
  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.
Personal tools