DekGenius.com
[ Team LiB ] Previous Section Next Section

2.1 Data

The Foundation framework provides many classes and protocols that extend the capabilities of the Objective-C language to represent and work with basic data types, such as strings and numbers, in an object-oriented fashion. Additionally, the Foundation framework provides application programming interfaces (APIs) for working with more complex data types, such as dates and collections.

2.1.1 Immutable Versus Mutable Classes

Classes such as NSString and NSArray are immutable classes; instances of these classes cannot be altered after they are initialized. Each immutable class, however, has a mutable subclass: for example, NSString has the mutable subclass NSMutableString, and NSArray has the subclass NSMutableArray. Mutable subclasses extend their superclass's functionality to allow modification after initialization. Immutable classes are more efficient, but mutable classes are more flexible.

2.1.2 Basic Types

Two of the most basic data types in an application are strings and numbers. The Foundation framework provides object abstractions in the form of NSString and NSNumber, and an extensive API to manipulate them.

2.1.2.1 Strings

Foundation's primary class used to represent and manipulate strings is NSString. Instances of NSString can be considered, at their core, an immutable array of Unicode characters, and can represent characters from the alphabets of nearly every written language, past and present. In fact, NSString is a class cluster, which shields the developer from a number of underlying implementation details that make string handling more efficient. This abstraction is generally relevant only when subclassing NSString, so it will not be considered further here.

Objective-C provides a syntax shortcut to create strings in code that is of the form @"...". In code, this looks like:

NSString *str = @"Hello";

When interpreted by the compiler, this syntax translates into an NSString object that is initialized with the 7-bit ASCII encoded string between the quotes. This string object is created at compile-time and exists for the life of the application. While you may send retain and release messages to an NSString object created from the literal syntax, such an object will never be deallocated. Example 2-1 shows several NSString methods. For more information on using printf-style formatting, see /Developer/Documentation/Cocoa/TasksAndConcepts/ProgrammingTopics/DataFormatting/iFormatStrings.html.

Example 2-1. Creating instances of, and working with, NSString
// The literal syntax for an NSString object
NSString *str = @"Hello";

// Create one string from another string
NSString *str2 = [NSString stringWithString:str];

// You can also create a string using printf style formatting
str = [NSString stringWithFormat:@"%d potatoes", 10];

// The contents of a text file may be used to initialize a string
str = [NSString stringWithContentsOfFile:@"/path/to/file"];

// C character arrays may be used to create a string as well
char *cStr = "Hello again";
str = [NSString stringWithCString:cStr]; 

// How to get a C string from an NSString
char cStr = [str UTFString];

// Determine the length of a string, which is a count of the
// number of Unicode characters in the string
unsigned int strLength = [str length];

// Append one NSString to another
// str2 = "Hello, World!"
str2 = [str stringByAppendingString:@", World!"];

// Append a format to an NSString
// str3 = "Hello, World! 2003"
NSString *str3 = [str2 stringByAppendingFormat:@" %d", 2003];

// Extract substrings; returns characters 6 to the end
// subStr = @"World! 2003"
NSString *subStr = [str3 substringFromIndex7];

// Returns characters from beginning to character 5
// subStr = @"Hello"
subStr = [str3 substringToIndex:5];

// Returns 6 characters starting at index 7; 
// Also see the comment that accompanies NSRange
// subStr = @"World!"
subStr = [str3 substringWithRange:NSMakeRange(7, 6)];

// Case conversion; returns capitalization: "Hello, World"
NSString *firstcaps = [str2 capitalizedString];

// Case conversion; returns lowercase: "hello, world!"
NSString *lower = [str2 lowercaseString];

// Case conversion; returns uppercase: "HELLO, WORLD!"
NSString *upper = [str2 uppercaseString];

// Searching for substrings; returns NSRange {0, 2}
NSRange loc = [str2 rangeOfString:@"He"];

// Searching for substrings; returns NSRange {NSNotFound, 0}
loc = [str2 rangeOfString:@"and"];

// Checking whether a string is a prefix or suffix of another
BOOL r = [str2 hasPrefix:@"Hello, W"];   // Returns YES
BOOL r = [str2 hasSuffix:@"What?"];      // Returns NO

NSRange is a Foundation data type used to specify a portion of a series. NSRange is defined as:

typedef struct _NSRange {
    unsigned int location;
    unsigned int length;
} NSRange;

The location is the starting index of the portion, and the length is the number of elements of the series in the range. Methods that return NSRanges set the location of the range to NSNotFound to indicate an invalid range in the context of the operation.

To initialize an NSString from Unicode characters, first assemble a C array of the Unicode character codes, which are of the type unichar. Example 2-2 shows how to use hexadecimal character codes to specify the Unicode characters for the string "abgd":

Example 2-2. Working with Unicode strings and NSString objects
// Create the unichar string "abgd"
unichar uc[5] = {0x03b1, 0x03b2, 0x03b3, 0x03b4, 0x03b5};

// Initialize an NSString with a Unicode string
NSString *uStr = [NSString stringWithCharacters:&uc length:5];

// Copy the Unicode characters into a buffer
unichar uc2[5] = [uStr characterAtIndex:0];

The entire Unicode character set catalog is available at http://www.unicode.org. This site offers a way for you to find the hexadecimal code for any character. In addition, Mac OS X also provides the Character Palette utility, found in the Input Menu, which can be used to look up character codes of any Unicode character. In Example 2-2, the Unicode characters were specified by their hexadecimal code because the default text encoding of source files (Mac OS Roman or another 8-bit encoding) doesn't allow direct representation of Unicode characters. However, Project Builder lets you specify Unicode (UTF-16 and UTF-8) as the text encoding for a source file, which would let you enter the Unicode characters directly into source strings with the Character Palette. The file encoding is specified globally in the Project Builder preferences, or on a per-file basis in the file's Project Builder info panel.

NSString includes a method used to break a string apart into components, based on a given separator character or string. This might be useful if you need to parse a record line from a text file whose fields are delimited by a character or string. Example 2-3 shows how this works for a string with colon-separated fields.

Example 2-3. Breaking a string up into its components
// A sample record from some record set
NSString *rec = @"John:Doe:Austin:TX:etc";        

// Break the string into components separated by colons
// Returns the array {John, Doe, Austin, TX, etc}
NSArray *fields = [str componentsSeperatedByString:@":"];

// NSArray can be used to rejoin the components into one string
// Returns "John*Doe*Austin*TX*etc"
NSString *rec2 = [fields componentsJoinedByString:@"*"];

NSMutableString extends NSString's functionality to support in-place modification. This additional flexibility is provided at the expense of decreased efficiency. Example 2-4 illustrates several commonly used methods in NSMutableString.

Example 2-4. Using NSMutableString
// Create a mutable string from an immutable string
NSString *str = @"Hello, World";
NSMutableString *ms = [NSMutableString stringWithString:str];

// Append one string to another, ms is now "Hello, World!"
[ms appendString:@"!"];

// Insert strings within a string
// ms is now "He_garbage_llo, World!"
[ms insertString:@"_garbage_" atIndex:2];

// Delete part of a string, ms is now "Hello, World!"
[ms deleteCharactersInRange:NSMakeRange(2,9)];

// Replace part of a string with another string
// ms is now "Hello, World."
[ms replaceCharactersInRange:NSMakeRange(12,1) withString:@"."];

// Replace the contents of a string with another string
[ms setString:@"That's all for now."];
2.1.2.2 Comparing strings

NSString provides several methods for comparing strings and testing equality. NSObject declares the method isEqual: to test general object equality. This method works with NSString objects, but the NSString method isEqualToString: more efficiently tests the equality of two objects known to be strings. Using it returns YES if the ids of the two strings are equal (which implies that the variables point to the same object) or if the result of a lexical comparison between the strings is NSOrderedSame.

A comparison that determines the lexical ordering of two strings is carried out with any of several methods, each of which provides varying degrees of control over the scope of the comparison. The method that provides the greatest amount of control is compare:options:range:. The options: argument takes one or both of the following two constants (both can be used with the C bitwise OR operator, |):

NSCaseInsensitiveSearch

Makes the comparison case insensitive.

NSLiteralSearch

Compares the two strings on a byte-by-byte, rather than character-by-character, basis. This comparison can improve speed for some operations, but differing literal sequences may not match when they otherwise would. For example, accented characters may be represented by a composite character (e.g., é), or a combined sequence of two Unicode characters (e.g., e and ´).

The range: argument restricts the comparison to a substring of the receiver. If you want to compare only the first two string characters, specify an NSRange of (0,2) in the range: argument.

Two other related methods are compare:options: and compare:. The first method passes options: to compare:options:range: and makes the range equal to the entire length of the receiver. The second, compare:, passes no options, and again uses the full extent of the receiver as the range. Example 2-5 shows different ways to compare strings.

Example 2-5. Comparing strings
NSString *a = @"Right";

// Test for equality; returns YES
BOOL v = [a isEqualToString:@"Right"];

// Determine lexical order of two strings; returns NSOrderedSame
NSComparisonResult r = [a compare:@"Right"];

// Returns NSOrderedDescending; light comes before Right
r = [a compare:@"light"];

// Returns NSOrderedAscending; sight comes after Right
r = [a compare:@"sight"];

// Literal, case-insensitive comparison by setting options
r = [a compare:@"right"
       options:NSCaseInsensitiveSearch | NSLiteralSearch];

// Easier case-insensitive comparison; returns NSOrderedSame
r = [@"next" caseInsensitiveCompare:@"NeXT"];
2.1.2.3 Attributed strings

NSAttributedString provides an API for text strings that contain information about graphical attributes of the text, such as its font, color, size, and kerning. Attributes can be applied to individual characters, ranges of characters, or to the entire length of the string. Like NSString, NSAttributedString is an immutable class with a mutable subclass, NSMutableAttributeString.

The functionality of NSAttributedString as it exists in the Foundation framework is fairly basic. Foundation's functionality is limited to keeping track of the string contents, as well as the various sets of attributes that apply to different ranges of the string. The Application Kit provides most functionality of NSAttributedString related to drawing and displaying text, and is covered more in Chapter 3 and Chapter 4.

2.1.2.4 Working with strings: character sets and scanners

In addition to a rich abstraction for strings, Foundation includes two classes that support string processing: NSScanner and NSCharacterSet.

2.1.2.5 NSCharacterSet

An NSCharacterSet represents a collection of Unicode characters. A number of sets are predefined and accessible through class methods, including:

  • alphanumericCharacterSet

  • capitalizedLetterCharacterSet

  • controlCharacterSet

  • decimalDigitCharacterSet

  • letterCharacterSet

  • punctuationCharacterSet

  • whitespaceAndNewlineCharacterSet

  • whitespaceCharacterSet

You can also create a new character set from a string, using characterSetWithCharactersInString:, load in a set from a file with characterSetWithContentsOfFile:, or invert an existing set with invertedSet:.

NSCharacterSet's mutable subclass, NSMutableCharacterSet, allows you to, amongst other modifications, add or remove string characters to or from a set and form a union or intersection with another set. Mutable character sets are, however, less efficient than immutable character sets. If you do not need to change a character set after establishing it, create an immutable copy with copy, and use that.

You would typically use NSCharacterSets to group characters, to let you find part of a particular set when searching an NSString object. You might use NString's rangeOfCharacterFromSet:options:range: method (or a variant thereof) to find the range in the receiver of the first (or in the case of a backwards search, last) character found from the set argument. NSCharacterSets are also used extensively with NSScanner.

2.1.2.6 NSScanner

An NSScanner object lets you search an NSString object for string and number values, with options for scanning up to or past characters from a given set or string. You would usually initialize a scanner with the string to scan, using scannerWithString: or initWithString. You can configure it to be case-sensitive, or not, with setCaseSensitive; establish a starting point with setScanLocation:; or set its locale with setLocale:. A scanner's locale affects the way it interprets values from the string. In particular, a scanner uses the locale's decimal separator to distinguish the integer and fractional parts of floating-point representations.

After it is configured, a scanner can read numeric values from its string into a variable, using methods such as scanInt:, scanFloat:, and scanDecimal: (the first two methods read scalars; scanDecimal: creates an NSDecimalNumber object). You can search for particular strings or characters by using any of the following methods:

  • scanString:intoString:

  • scanUpToString:intoString:

  • scanCharactersFromSet:intoString:

  • scanUpToCharactersFromSet:intoString:

All of these methods return a Boolean value to indicate the operation's success. Pass a pointer to the variable as the argument to these methods, or pass nil to skip a value. Finally, check whether you have reached the end of the input string with the isAtEnd method. For example, assume a file, ~/scannerTest.txt, of the form:

EmpId: 7830480 FirstName: Jo LastName: Wong
EmpId: 67567456 FirstName: Toni LastName: Jones
EmpId: 546776 FirstName: Dylan LastName: Blimp

Example 2-6 shows how the file may be parsed with NSScanner.

Example 2-6. Using NSScanner and NSCharacterSet
NSCharacterSet * letterSet , *whiteSet;
letterSet = [NSCharacterSet letterCharacterSet];
whiteSet = [NSCharacterSet whitespaceAndNewlineCharacterSet];

NSString *filePath, *fileString;
NSScanner *scanner;

filePath = [@"~/scannerTest.txt" stringByExpandingTildeInPath];

fileString = [NSString stringWithContentsOfFile:filePath];

scanner = [NSScanner scannerWithString:fileString];

while ( ![scanner isAtEnd] )  {
  NSString *fName, *lName;
  int empId;

  if ( [scanner scanString:@"EmpId: " intoString:nil] ) {

    [scanner scanInt:&empId];

    [scanner scanString:@"FirstName: " intoString:nil];
    [scanner scanCharactersFromSet:letterSet intoString:&fName];

    [scanner scanString:@"LastName: " intoString:nil];
    [scanner scanCharactersFromSet:letterSet intoString:&lName];

    NSLog(@"%@ %@, EmpID: %d", fName, lName, empId);

    [scanner scanCharactersFromSet:whiteSet intoString:nil];
  }
}

The code in Example 2-6 produces the following output:

Jo Wong, EmpID: 7830480
Toni Jones, EmpID: 67567456
Dylan Blimp, EmpID: 546776
2.1.2.7 Numbers

For many numerical operations dealing with calculations, using C's primitive numerical data types is the easiest and most efficient way to represent numerical data. However, you might need to treat a number as an object to store it in a collection or to store a number in the user defaults database. For such situations, the Foundation framework provides the class NSNumber, which is an Objective-C wrapper class for the standard numeric data types in C. You can initialize instances of NSNumber with a scalar and retrieve a scalar value from a number object. Example 2-7 shows many of the methods used to work with NSNumbers.

Example 2-7. Working with NSNumber
// NSNumbers can contain any primitive C type
NSNumber *iN = [NSNumber numberWithInt:1];
NSNumber *fN = [NSNumber numberWithFloat:50.5f];
NSNumber *dN = [NSNumber numberWithDouble:100.45];
NSNumber *cN = [NSNumber numberWithChar:100];
NSNumber *lN = [NSNumber numberWithLong:100];
NSNumber *usN = [NSNumber numberWithUnsignedShort:30];

// Access the value of an NSNumber object
int i = [iN intValue];                           // Returns 1
float f = [fN floatValue];                       // Returns 50.5
double d = [dN doubleValue];                     // Returns 100.45
char c = [cN charValue];                         // Returns 100
long l = [lN longValue];                         // Returns 100
unsigned short us = [usN unsignedShortValue];    // Returns 30

// Test for equality of two numbers; returns YES
BOOL b = [nc isEqualToNumber:nl];

// Determine how one number compares to another in order
NSComparisonResult r = [nc compare:nus];         // NSOrderedDescending
r = [nus compare:ns];                            // NSOrderedAscending

NSDecimalNumber extends the capabilities of NSNumber with APIs to perform base-10 arithmetic, and it provides methods to initialize an instance in terms of the number's basic components. Example 2-8 shows how to work with NSDecimalNumber.

Example 2-8. Working with NSDecimalNumber
// Planck's Constant (1.04e-34) 
NSDecimalNumber *h = [NSDecimalNumber 
                            decimalNumberWithManitssa:104
                            exponent:-36
                            isNegative:NO];

// NSDecimalNumber has methods for returning commonly used numbers
NSDecimalNumber *one = [NSDecimalNumber one];        // 1.0
NSDecimalNumber *zero = [NSDecimalNumber zero];      // 0.0

// NSNumbers that represent system limits
NSDecimalNumber *max = [NSDecimalNumber maximumDecimalNumber];
NSDecimalNumber *min = [NSDecimalNumber minimumDecimalNumber];

// Methods to operate on the numbers
NSDecimalNumber *n;
n = [one decimalNumberByAdding:zero];         // n = 1.0
n = [one decimalNumberBySubtracting:zero];    // n = 1.0
n = [h decimalNumberByMultiplyingBy:c];       // n = 3.16e-26
n = [h decimalNumberByDividingBy:one];        // n = 1.05e-34
n = [c decimalNumberByRaisingToThePower:2];   // n = 9.0e16

Each method has corresponding methods that let you determine how to handle rounding and errors (typically by using instances of NSDecimalNumberHandler), which is known as the number's behavior. Numbers round to the nearest integer by default; an exception is raised if there is division by zero, or if the result of a calculation exceeds the maximum or minimum numbers that can be represented.

2.1.3 Collections

The Foundation framework offers several important classes for creating and manipulating collections of objects. The primary collection classes are NSArray, NSDictionary, and NSSet:

NSArray

Stores an ordered, immutable collection of objects, where each member is referenced by its index number. Any given object may appear in an array more than once.

NSSet

Stores an immutable, unordered collection of unique objects, which support the mathematical idea of a set. NSSet objects are useful when your collection requires you to test an object for membership; NSSet provides a more efficient implementation for testing object membership over that of other collection classes.

NSDictionary

Stores a collection of objects as key-value pairs; each member has an associated key that identifies that member object.

Each class has subclasses that extend their interfaces to provide mutability.

2.1.3.1 Arrays

Instances of NSArray represent an ordered collection objects. An index number identifies each member object in the array; indexing begins at zero, just as in C arrays. Example 2-9 gives an overview of NSArray's capabilities.

Example 2-9. Creating and working with NSArray objects
// Create an array from several objects
// Objects are separated by commas, and the list must end with nil
NSArray *a = [NSArray arrayWithObjects:@"Hello",@"how",@"are", @"you",nil];

// If you need an array with one object
NSArray *b = [NSArray arrayWithObject:@"One object"];

// Create an array from the contents of an XML property list
b = [NSArray arrayWithContentsOfFile:@"/path/to/plist"];

// Test arrays for equality
BOOL r = [a isEqualToArray:b];                // Returns NO

// Determine the number of memebers in a collection
int n = [a count];                           // Returns 4

// Access elements of the array
NSString *one = [a objectAtIndex:0];        // Returns @"Hello"
NSString *end = [a lastObject];             // Returns @"you"

// Discover the index of an object
unsigned idx = [a indexOfObject:@"how"];     // Returns 1

// Find out if an array contains some object
BOOL result = [a containsObject:@"today"];           // Returns NO

// Obtain a new array by adding an object
NSArray *newA = [a arrayByAddingObject:@"today"];

// Extract subarrays
NSArray *subA = [a subarrayWithRange:NSMakeRange(1,2)];

NSMutableArray extends NSArray by adding support for arrays whose contents can be changed after their initialization. Example 2-10 shows how a small set of the mutability methods works.

Example 2-10. A sampling of NSMutableArray methods
// Create a mutable array from an immutable array
NSMutableArray *ma = [NSMutableArray arrayWithArray:a];

// Create an empty mutable array
NSMutableArray *ma = [NSMutableArray array];

// Exercise mutability
[ma addObject:@"World"];                    // ma is {World}
[ma insertObject:@"Hello" atIndex:0];       // ma is {Hello, World}
[ma removeObjectAtIndex:0];                         // ma is {World}
[ma removeLastObject];                      // ma is {}
2.1.3.2 Sets

NSSet declares an interface to unordered collections of unique objects. The Foundation framework implements two subclasses of NSSet: NSMutableSet and NSSCountedSet, which is a child class of NSMutableSet. Like arrays and dictionaries, the contents of a set can be any Objective-C object. Example 2-11 shows how to use NSSet.

Example 2-11. Using NSSet
// Create a set from the contents of an array
NSSet *set1 = [NSSet setWithArray:anArray];        

// Create a set from arbitrary objects
set1 = [NSSet setWithObjects:@"a", @"b", @"c",@"d", nil];

// Create a set from a single object
NSSet *set2 = [NSSet setWithObject:@"a"];

// Determine the size of the set
unsigned int n = [set1 count];              // Returns 4

// Access set members; creates an NSArray from the set contents
NSArray *setObjs = [set1 allObjects]; 

// You can have a set randomly (essentially) return a member
id object = [set1 anyObject];

// Test for membership, the strength of NSSet; returns YES
BOOL b = [set1 containsObject:@"a"];
b = [set1 containsObject:@"z"];             // Returns NO
id mem = [set1 member:@"a"];                // Returns @"a"
id mem = [set1 member:@"z"];                // Returns nil

// Compare two sets
NSSet *set3 = [NSSet setWithObjects:@"c", @"d", @"e", @"f", nil];
BOOL b = [set2 isSubsetOf:set1];            // Returns YES
b = [set2 intersectsSet:set1];              // Returns YES
b = [set3 intersectsSet:set1];              // Returns NO
b = [set1 isEqualToSet:set2];               // Returns NO

Example 2-12 shows what NSMutableSet adds to NSSet.

Example 2-12. Methods provided by NSMutableSet
// Add and remove member objects
[set1 addObject:@"e"];              // set1 now [a, b, c, d, e]
[set1 removeObject:@"a"];           // set1 now [b, c, d, e]
[set2 removeAllObjects];            // set1 now []

// Combine sets
[set1 unionSet:set3];               // set1 now [b, c, d, e, f]
[set1 minusSet:set3];               // set1 now [b]
[set1 intersectSet:set3];           // set1 now []
[set1 setSet:set3];                 // set1 now [c, d, e, f]

NSCountedSet is based on a slightly different idea of a set than its superclasses. In a standard set, each member object must be unique. Counted sets remove the constraint on uniqueness, making it possible to add an object to a counted set more than once. However, a counted set does not keep multiple references to an object; rather, it keeps a count of the number of times an object was added to the set. Whenever an object is added to the set of which it is already a member, the count for that object is incremented. When an object is removed from a counted set, its count is decremented until it reaches zero, at which point the object is no longer a member of the set. The code in Example 2-13 demonstrates the functionality added by NSCountedSet.

Example 2-13. Methods provided by NSCountedSet
// Add and remove objects; inherits methods from NSMutableSet
[set3 addObject:@"b"];              // set3 now [b, c, d, e, f]
[set3 addObject:@"b"];              // Increments count for b to 2
[set3 addObject:@"b"];              // Count for b now 3
[set3 countForObject:@"b"];         // Returns 3
[set3 removeObject:@"b"];
[set3 countForObject:@"b"];         // Returns 2
2.1.3.3 Dictionaries

Cocoa dictionaries provide a collection class that implements the idea of key-value pairs. In a dictionary, member objects are associated with a unique identifier key, used to identify and access the object. Although keys are typically NSString objects, both keys and values may be of any class. Example 2-14 summarizes several commonly used methods of NSDictionary.

Example 2-14. Working with NSDictionary
// Create an empty dictionary, useful for
// creating empty mutable dictionaries
NSDictionary *d = [NSDictionary dictionary];

// Initialize a dictionary with contents of an XML property list
d = [NSDictionary dictionaryWithContentsOfFile:@"pList"];

// Create a dictionary from one object with a key
d = [NSDictionary dictionaryWithObject:@"a" forKey:@"A"];

// Create a dictionary with many objects and keys
d = [NSDictionary dictionaryWithObjects:@"a", @"b", nil
                                 forKeys:@"A", @"B", nil];

// Count the number of objects in the dictionary; 
int n = [d count];                              // Returns 2

// Access objects and keys;
id obj = [d objectForKey:@"A"];                 // Returns "a"

// Returns nil since "a" is not a valid key
obj = [d objectForKey:@"a"];

// Returns an array whose members are the keys of the receiver
NSArray *k = [d allKeys];

// Returns an array with the dictionary's objects
NSArray *v = [d allValues];

// Returns an enumerator for the receiver's keys
NSEnumerator *e = [d keyEnumerator];

// Returns enumerator for objects in dictionary
e = [d objectEnumerator];

// Write contents of dictionary to a file formatted
// as an XML property list
[d writeToFile:@"/path/to/file" atomically:YES];

Example 2-15 shows how to work with mutable dictionaries.

Example 2-15. Working with NSMutableDictionary
// Create a mutable dictionary from an immutable dictionary
NSMutableDictionary *md;
md = [NSMutableDictionary dictionaryFromDictionary:d];

// Add a key-value pair
[md setObject:@"c" forKey:@"C"];

// Remove an object from the dictionary
[md removeObjectForKey:@"A"];

// You can also remove all objects in one fell swoop
[md removeAllObjects];

// Finally, replace the current contents with the
// contents of another dictionary
[md setDictionary:d];
2.1.3.4 Enumerators

Traditionally, a for-loop is used to enumerate the contents of a collection, which provides access to each member by its index. Since the for-loop technique depends on indexed collection contents, it won't work for non-indexed collections, such as sets and dictionaries. NSEnumerator provides an object-oriented way of iterating over the contents of any collection. Each Foundation collection type implements the method objectEnumerator, which returns an enumerator for the receiver.

To illustrate how NSEnumerator is used in place of the for-loop, consider Examples Example 2-16 and Example 2-17. Example 2-16 shows how an array is traditionally enumerated using a for-loop.

Example 2-16. Using a for-loop to enumerate an array's contents
// Assume NSArray *array exists
int i;
id object;

for ( i = 0; i < [array count]; i++ ) {
    object = [array objectAtIndex:i];

    // Do something with the object
}

Example 2-17 shows how the NSEnumerator class accomplishes the same task.

Example 2-17. Using NSEnumerator to enumerate an array's contents
// Assume NSArray *array exists
NSEnumerator *e = [array objectEnumerator];
id object; 

while ( object = [e nextObject] ) {

    // Do something with the object
}

Some collection classes have variations on the standard objectEnumerator method. For example, the reverseObjectEnumerator method of NSArray lets you access the array's contents from the last item to the first. Another variation is NSDictionary's keyEnumerator method, which lets you enumerate the dictionary's keys instead of its values.

Since the members of an array are indexed, expect an enumerator to return the contents of an array in a predictable order. NSDictionary and NSSet, on the other hand, don't store their contents in a meaningful order, so the order in which the enumerators return the members is unpredictable.

2.1.3.5 Memory management in collections

Whenever an object is added to a collection, the collection object sends that object a retain message, asserting some ownership over the object that is now a member of the collection. This is true whether the object is added as part of the collection initialization, or at a later point with the addObject:-based methods in mutable collection classes. Objects that are removed from collections receive a release message, as the collection no longer has any interest in maintaining ownership over the object. When a collection is deallocated, all member objects are sent a release message. Example 2-18 shows how this works in practice.

Example 2-18. Collection memory management
// anObject has reference count of 1
id anObject = [[ObjectClass alloc] init];

// Assume anArray is an existing mutable array; reference
// count of anObject is now 2.
[anArray addObject:anObject];

// anObject reference count now 1, still valid because of
// retain sent by the array in addObject:
[anObject release];

// Either of these actions will cause anObject to be released
[anArray removeObject:anObject];
[anArray release];

2.1.4 Dates and Time

Cocoa provides three classes to represent date and time information: NSDate, NSCalendarDate, and NSTimeZone. NSDate represents an instant in time, to millisecond precision, as the number of seconds since the absolute reference time, midnight (GMT), January 1, 2001. Many NSDate methods work with time intervals. A time interval is represented by the Foundation data type NSTimeInterval (which is a redefinition of the primitive type double). NSTimeIntervals specify a length of time in units of seconds. Example 2-19 shows how to use NSDate.

Example 2-19. Fundamental methods of NSDate
// Create an NSDate set to the current date
NSDate *today = [NSDate date];

// Obtain a date that is many centuries in the future
NSDate *future = [NSDate distantFuture];

// Similarly, obtain a date that is many centuries in the past
NSDate *past = [NSDate distantPast];

// A date that is some number of seconds past the system reference
// date (or before if you supply a negative value)
NSDate *intvl = [NSDate dateWithTimeIntervalSinceReferenceDate:60];        

// Check for equality of two dates; returns NO
BOOL b = [today isEqualToDate:intvl];

// These methods return either the earlier or
// the later of the two dates involved.
NSDate *d = [today earlierDate:past];       // Returns past
NSDate *d = [today laterDate:future];       // Returns future

// Obtain Time Intervals
NSTimeInterval d = [intvl timeIntervalSinceReferenceDate];

// Number of seconds between receiver date and current date
d = [today timeIntervalSinceNow];

// Number of seconds between the two dates
d = [today timeIntervalSinceDate:[NSDate date]];

// Number of seconds since 1970, another reference date
d = [today timeIntervalSince1970];

NSDate is a lightweight class that represents dates as points in time. NSCalendarDate, a subclass of NSDate, can additionally perform date arithmetic based on the Western Gregorian calendar. NSCalendarDate expands the functionality of NSDate to provide methods that work with dates in terms of days, weeks, months, and years. Example 2-20 summarizes what you can do with NSCalendarDate.

Example 2-20. Working with NSCalendarDate
// Create an NSCalendarDate
NSCalendarDate *cd = [NSCalendarDate calendarDate];        

// Create an arbitrary calendar date
cd = [NSCalendarDate dateWithYear:2002 month:4 day:10 hour:20
                                  minute:3 second:0
                                  timeZone:[NSTimeZone systemTimeZone]];

// Retrieve elements of a calendar date
int dce = [cd dayOfCommonEra];      // Returns 730950
int dm = [cd dayOfMonth];           // Returns 10
int dw = [cd dayOfWeek];            // Returns 3
int d = [cd dayOfYear];             // Returns 100
int h = [cd hourOfDay];             // Returns 20
int m = [cd minuteOfHour];          // Returns 3
int s = [cd secondOfMinute];        // Returns 0
int y = [cd yearOfCommonEra];       // Returns 2002

Associated with every NSCalendarDate object is an NSTimeZone object. Instances of NSTimeZone capture information about geographic time zones across the planet, such as their name, abbreviation, and "distance" from the reference time zone, GMT, in seconds. Additionally, NSTimeZone is aware of daylight savings time, and capable of translating dates between time zones. NSCalendarDate still stores a date in its lowest form as a time interval from the reference date, which is behavior it inherits from NSDate. NSTimeZone translates that time interval from GMT to a specific time zone. The systemTimeZone method used in Example 2-20 is just one method of NSTimeZone that returns the time zone set on your system. In addition to this method, NSTimeZone declares several other methods, some of which are shown in Example 2-21.

Example 2-21. Working with NSTimeZone
// Create time zone objects
NSTimeZone *tz = [NSTimeZone timeZoneWithAbbreviation:@"CST"];

// Obtain the geo-political name of the time zone
// Returns "America/Chicago"
NSString *name = [tz name];

// Get the time zone's abbreviation; Returns CST
NSString *abv = [tz abbreviation];

// Returns whether or not it is daylight saving time; returns NO
BOOL b = [tz isDaylightSavingTime];        

// The time difference relative to GMT in seconds; Returns -18000
int s = [tz secondsFromGMT];

2.1.5 Binary Data

NSData encapsulates a buffer of bytes. Many Foundation framework classes have methods that let you initialize an object from an instance of NSData or convert the object's contents into an NSData object. NSData is a generic object that lets you store and transport data of any kind, any way you like. Example 2-22 gives an example.

Example 2-22. Working with NSData
// NSData objects can be created to hold the contents of any data
// buffer, such as a static C character string
char *cData = "This is data, a string of bytes";
NSData *data = [NSData dataWithBytes:cData length:strlen(cData)];

// Create NSData objects from files
data = [NSData dataWithContentsOfFile:@"/path/to/file"];

// Create data objects from resources located by NSURL objects
data = [NSData dataWithContentsOfURL:URLObject];

// Get a C pointer to the data object contents
void *p = [data bytes];

// Copy the contents of data object into a buffer
char buffer[50];
[data getBytes:(void *)buffer];

// Copy a specified number of bytes into the buffer
[data getBytes:buffer length:4];

// Copy a range of bytes from the data object into the buffer
[data getBytes:buffer range:NSMakeRange(5,2)];

// Determine the number of bytes in the data
unsigned l = [data length];

Note in the second line that despite initializing an NSData object with a C string, the NSData object is not a string. The data object has no idea what its contents represent, only that it is a collection of bytes. The client that interacts with the data object is responsible for knowing how to interpret the contents.

Like many other Foundation classes, NSData is an immutable class that has a mutable child class, NSMutableData. NSMutableData adds methods to change the length of the stored data (how many bytes are in there) and append data to the stored data, as illustrated in Example 2-23.

Example 2-23. Working with NSMutableData
// Create an empty NSData object
NSMutableData *mData = [NSMutableData data];

// Set the size of the internal NSData buffer to 29 bytes
[mData setLength:29];

// Take the data from a buffer and place it 
// into the NSData object
[mData appendBytes:cData length:29];
    [ Team LiB ] Previous Section Next Section