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.
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.
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.
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.
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:
Declares an object of type NSString
, named
artist
, and sets it to the value
"Underworld"
Obtains the length
of the string and prints it
using the NSLog
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 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.
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.
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 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.
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 00"...
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.
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:
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.
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 ", range.location); // c NSString * title = [song substringToIndex:range.location]; // d NSString * artist = [song substringFromIndex:range.location + range.length]; // e printf("title: %s ", [title UTF8String]); // f printf("artist: %s ", [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 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
character to print out the
new line.
Declares a string named title
and sets it to the
substring, from the start of the song
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 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.
Prints the title
to the console, using the UTF-8
representation of the string. Notice that we are using the
printf %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
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.
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 ", [song UTF8String]); // c NSRange range = [song rangeOfString:@"Deaf"]; // d [song replaceCharactersInRange:range withString:@"Def"]; // e printf("%s ", [song UTF8String]); // f [song insertString:@"Animal by " atIndex:0]; // g printf("%s ", [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 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.
Build and run (
-R) the application. You should see the following output in the console:
Deaf Leppard Def Leppard Animal by Def Leppard
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:
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.
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 ", [filename UTF8String]); // c NSString * source = [NSString stringWithContentsOfFile:filename]; // d printf("%s ", [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 ", [filename UTF8String]); NSString * source = [NSString stringWithContentsOfFile:filename]; printf("%s ", [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.
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—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 ", i, [[array objectAtIndex:i] UTF8String]); // d } [pool release]; return 0; }
The code we added performs the following tasks:
Declares a new string.
Creates an array of string objects using the
componentsSeparatedByString:
method of the
NSString
class. Note that in the first example of
this chapter, where we looked for the range of the comma to split the
spring, we could have used this method to get the two strings.
Obtains the count of the array to use in the for
loop.
Prints each item of the array to the console.
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
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.
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:
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.
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 ", [[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")
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 )
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 ", [lastName UTF8String], [firstName UTF8String]); // g } [pool release]; return 0; }
The code we added performs the following tasks:
Imports the AddressBook API set. Without this line, the compiler cannot compile the main.m file, because it won’t be able to find the definitions for the Address Book classes.
Obtains the Address Book for the logged-in user.
Obtains an array containing all of the people in the Address Book.
Loops through the people to obtain an ABPerson
object. The ABPerson
class provides the methods to
work with the various attributes that a person record has in the
Address Book database.
Gets the first name of the person.
Gets the last name of the person.
Prints the name of the person out to the console.
Build and run (
-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—implemented
by the NSSet
and
NSMutableSet
classes—are an unordered collection
of objects in which each object can appear only once. A set can be
used instead of an
array when the order of elements in the
collection is not important, but when testing to see if an object is
part of the set (usually referred to as “testing for
membership”), speed is important. Testing to see if
an object is a member of a set is faster than testing against an
array.
Dictionaries—implemented
in the
NSDictionary
class—store and retrieve objects
using key-value
pairs. Each
key-value pair in a dictionary is called an
entry
. The keys in a dictionary form a
set; a key can be used only once in a dictionary. Although the key is
usually a string (an NSString
object), most
objects can be used as keys.[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.
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 ", [[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.
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.
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:
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.
#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 ", [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 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.
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.
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.
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.
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.
The important things to remember about memory management in Cocoa distill down to these rules of thumb:
Objects created by alloc
or
copy
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
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.
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.
Investigate the lowercase
and
uppercase
methods of NSString
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
, and
NSDictionary
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.