Chapter 4. The Cocoa Foundation Kit
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.
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.
-
- (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:
In Project Builder, create a new Foundation Tool (File → New Project → Tool → Foundation Tool) named “strings”, and save it in your ~/LearningCocoa folder.
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.
Open the main.m file, located in the “Source” group, and modify it to match the code shown in Example 4-1.
Example 4-1. Working with stringsint 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:
Declares an object of type
NSString
, namedartist
, and sets it to the value"Underworld"
Obtains the
length
of the string and prints it using theNSLog
function
Build and run (
-R) the application. You will be prompted to save the main.m file, 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 theartist
object was substituted for the%@
token and that the return value from thelength
method was substituted for the%d
token. Remember, you can use any of the standardprintf
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.
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.Build and debug the application (Build → Build and Debug, or
-Y). Execution will start and then pause at the breakpoint we set, highlighting the line at which it stopped in a salmon colored bar.
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.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.
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.
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
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
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.
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 the10
denotes the number of characters (orlength
) in theartist
object. Note that we needed to cast the return type from thelength
message so that theprint
command could operate. Try this again without the(int)
cast.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 ourartist
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 thelocation
andlength
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:
In Project Builder, create a new Foundation Tool (File → New Project → Tool → Foundation Tool) named “substrings”, 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-2.
Example 4-2. Working with substringsint 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:
Declares a string object named
song
and sets it.Obtains the range of the comma in the
song
string.Prints the location of the comma. Notice that we are using the standard C
printf
function here. We will useprintf
instead ofNSLog
in many of the upcoming exercises, so the output from our programs won’t be cluttered with timestamps and PIDs. Note that, unlike theNSLog
function, we have to be sure to include the\n
character to print out the new line.Declares a string named
title
and sets it to the substring, from the start of thesong
string to the location of the comma.Declares a string named artist and sets it to the substring, from the comma to the end of the
song
string. We use therange.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.Prints the
title
to the console, using the UTF-8 representation of the string. Notice that we are using theprintf %s
token.Prints the
artist
to the console, using the UTF-8 representation of the string.
Build and run (
-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:
In Project Builder, create a new Foundation Tool (File → New Project → Tool → Foundation Tool) named “mutablestrings”, 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]; 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:
Creates a new empty mutable string named
song
.Appends the contents of the “Deaf Leppard” string to the
song
mutable string.Prints the
song
mutable string to the console.Gets the range of the “Deaf” substring.
Replaces the “Deaf” substring with “Def” to correct the misspelling.
Prints the
song
mutable string to the console.Inserts the string “Animal by” at the beginning the mutable string.
Once again prints the
song
mutable string.Releases the
song
object. Because we created theSong
object using thealloc
method, we are responsible forr releasing it. We’ll explain more about how this works later in this chapter.
Build and run (
-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 returnmain.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 valuem
.-
- (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 *)pathatomically:
(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:
In Project Builder, create a new Foundation Tool (File → New Project → Tool → Foundation Tool) named “filestrings”, 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-3.
Example 4-3. Reading files into stringsint 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:
Creates a string object, named
filename
, that contains the path to the main.m source file of this project. Note that you must save your project in your ~/LearningCocoa folder for this example to work. If you are saving your projects to some other location, you will need to edit the path appropriately.Sets the
filename
variable to a standardized path. This will resolve the~/
characters to your home directory.Prints the resolved
filename
variable.Creates a new string, named
source
, with the contents of the main.m source file.Prints the
source
string to the console.
Build and run (
-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.
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.Build and debug the application (
-Y). Execution will start and then pause at the breakpoint.
Click on the Console tab above the variable viewer to open up the debugger console.
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
Type in the following at the
(gdb)
prompt:(gdb) print-object [filename pathExtension]
When you enter this command, you should see the following:
m
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.
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[12]
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:
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 theNSString
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.
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
Using the debugger to explore NSArray
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.Build and debug (
-Y) the application. Execution will start and then pause at the breakpoint we set.
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.
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)anObjectatIndex:
(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:
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.
Example 4-4. Working with mutable arraysint 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
Build and run (
-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:
Set a breakpoint before the line of code that releases the array (line 10).
Build and debug (
-Y) the application. Execution will start and then pause at the breakpoint.
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 )
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.
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. TheABPerson
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 (
-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.[13] 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:
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.
Example 4-5. Working with dictionariesint 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.
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; }
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.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.
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.
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)anObjectforKey:
(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.
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 *)pathatomically:
(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:
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.
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:
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 (
-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.
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.
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:
Objects created by
alloc
orcopy
have a retain count of 1.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.
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.Make sure that there are as many
release
orautorelease
messages sent to objects as there arealloc
,copy
,mutableCopy
, orretain
messages sent. In other words, make sure that the code you write is balanced.Retain, then release objects in setter methods.
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
Investigate the
lowercase
anduppercase
methods ofNSString
using the debugger.Write a Foundation Tool command-line application that prints the contents of any filename given to it.
Read the documentation on your hard drive about the
NSArray
,NSSet
, andNSDictionary
classes.Modify the arrays example application so that it saves the contents of the array to a file.
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.
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.)
[12] 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.
[13] 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.
Get Learning Cocoa with Objective-C, 2nd Edition now with the O’Reilly learning platform.
O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.