Cocoa is actually composed of two different frameworks: Foundation and Application Kit.
Cocoa’s Foundation framework has a bunch of useful low- level, data- oriented classes and types.
NSRange
typedef struct _NSRange {
unsigned int location;
unsigned int length;
} NSRange;
This structure is used to represent a range of things, usually a range of characters in a string or a range of items in an array.
You can make a new NSRange in three different ways.
NSRange range;
range.location = 17;
range.length = 4;
NSRange range = { 17, 4 };
NSRange range = NSMakeRange (17, 4);
// NSMakeRange can be used anywhere you need NSRange
[anObject flarbulateWithRange: NSMakeRange (13, 15)];
Geometric Types
NSPoint represents an (x, y) point in the Cartesian plane:
typedef struct _NSPoint {
float x;
float y;
} NSPoint;
NSSize holds a width and a height:
typedef struct _NSSize {
float width;
float height;
} NSSize;
Cocoa provides a rectangle type, which is a composition of a point and a size:
typedef struct _NSRect {
NSPoint origin;
NSSize size;
} NSRect;
Cocoa also provides corresponding make functions: NSMakePoint(), NSMakeSize(), and NSMakeRect().
NSString
stringWithFormat:
NSString’s stringWithFormat: method creates a new NSString just like that, with a format and arguments:
+ (id) stringWithFormat:(NSString *)format,...;
NSString *height;
height = [NSString stringWithFormat:
@"Your height is %d feet, %d inches", 5, 11];
When the Objective- C runtime builds a class, it creates a class object that represents the class. The class object contains pointers to the superclass, class name, and to the list of the class’s methods. The class object also contains a long that specifies the size, in bytes, for newly created instance objects of that class.
When you declare a method with the plus sign, you’ve marked the method as a class method. This method belongs to the class object (as opposed to an instance object of
the class) and is typically used to create new instances. Class methods used to create new objects are calledfactory methods.
length
Another handy NSString method (an instance method) is length, which returns the number of characters in the string:
- (unsigned int) length;
unsigned int length = [height length];
NSString’s length method does the right thing when dealing with international strings, such as those containing Russian, Chinese, or Japanese characters, and using the Unicode international character standard under the hood. Dealing with these international strings in straight C is especially painful, because an individual character might take more than 1 byte. This means that functions like strlen(), which just counts bytes, can return the wrong value.
isEqualToString:
isEqualToString: compares the receiver (the object that the message is being sent to) with a string that’s passed in as an argument. isEqualToString: returns a BOOL (YES or NO) indicating if the two strings have the same contents. It’s declared like this:
- (BOOL) isEqualToString: (NSString *) aString;
NSString *thing1 = @"hello 5";
NSString *thing2;
thing2 = [NSString stringWithFormat: @"hello %d", 5];
if ([thing1 isEqualToString: thing2]) {
NSLog (@"They are the same!");
}
compare:
compare: does a character-by- character comparison of the receiving object against the passed- in string. It returns an NSComparisonResult (which is just an enum) that shows the result of the comparison:
typedef enum _NSComparisonResult {
NSOrderedAscending = -1,
NSOrderedSame,
NSOrderedDescending
} NSComparisonResult;
- (NSComparisonResult) compare: (NSString *) string;
compare: does a case- sensitive comparison. In other words, @"Bork" and @"bork", when compared, won’t return NSOrderedSame. There’s another method, compare:options:, that gives you more control:
- (NSComparisonResult) compare: (NSString *) string
options: (unsigned) mask;
Check Substring
Prefix and Suffix:
- (BOOL) hasPrefix: (NSString *) aString;
- (BOOL) hasSuffix: (NSString *) aString;
NSString *filename = @"draft- chapter.pages";
if ([fileName hasPrefix: @"draft") {
// this is a draft
}
if ([fileName hasSuffix: @".mov") {
// this is a movie
}
If you want to see if a string is somewhere inside another string, use rangeOfString
When you send rangeOfString: to an NSString object, you pass it the string to look for. It then returns an NSRange struct to show you where the matching part of the string is and
how large the match is.
- (NSRange) rangeOfString: (NSString *) aString;
NSRange range;
range = [fileName rangeOfString: @"chapter"];
If the argument isn’t found in the receiver, range.start will be equal to NSNotFound.
Mutability
NSStrings are immutable. You can do all sorts of stuff with them, like make new strings with them, find characters in them, and compare them to other strings, but you can’t change them by taking off characters off or by adding new ones.
Cocoa provides a subclass of NSString called NSMutableString. Use that if you want to slice and dice a string in place.
You can create a new NSMutableString by using the class method stringWithCapacity:
+ (id) stringWithCapacity: (unsigned) capacity;
NSMutableString *string;
string = [NSMutableString stringWithCapacity: 42];
Once you have a mutable string, you can do all sorts of wacky tricks with it. A common operation is to append a new string, using appendString: or appendFormat:
- (void) appendString: (NSString *) aString;
- (void) appendFormat: (NSString *) format, ...;
NSMutableString *string;
string = [NSMutableString stringWithCapacity: 50];
[string appendString: @"Hello there "];
[string appendFormat: @"human %d!", 39];
You can remove characters from the string with the deleteCharactersInRange: method:
- (void) deleteCharactersInRange: (NSRange) range;
NSMutableString *friends;
friends = [NSMutableString stringWithCapacity: 50];
[friends appendString: @"James BethLynn Jack Evan"];
NSRange jackRange;
jackRange = [friends rangeOfString: @"Jack"];
jackRange.length++; // eat the space that follows
[friends deleteCharactersInRange: jackRange];
We get a couple of behaviors for free because NSMutableString is a subclass of NSString.
- The first freebie is that anywhere an NSString is used, we can substitute an NSMutableString.
- The other free behavior comes from the fact that inheritance works just as well with class methods as it does with instance methods.
NSMutableString *string;
string = [NSMutableString stringWithFormat: @"jo%dy", 2];
Collection Agency
NSArray
NSArray has two limitations.
- First, it will hold only Objective- C objects. You can’t have primitive C types, like int, float, enum, struct, or random pointers in an NSArray.
- Also, you can’t store nil (the zero or NULL value for objects) in an NSArray.
You can create a new NSArray by using the class method arrayWithObjects:. You give it a comma- separated list of objects, with nil at the end to signal the end of the list (which, by the way, is one of the reasons you can’t store nil in an array):
NSArray *array;
array = [NSArray arrayWithObjects:
@"one", @"two", @"three", nil];
You can get a count of the number of objects it contains and fetch an object at a particular index:
- (unsigned) count;
- (id) objectAtIndex: (unsigned int) index;
int i;
for (i = 0; i < [array count]; i++) {
NSLog (@"index %d has %@.",
i, [array objectAtIndex: i]);
}
If you refer to an index that’s greater than the number of objects in the array, Cocoa prints a complaint at runtime.
[array objectAtIndex: 208000];
*** Terminating app due to uncaught exception 'NSRangeException',
reason: '*** -[NSCFArray objectAtIndex:]: index (208000) beyond bounds (3)'
When you see the characters “CF” in Cocoa, you’re looking at something related to Apple’s Core Foundation framework. Core Foundation is like Cocoa but implemented in C, and much of it is open source if you want to download it and poke around. Many Core Foundation objects and Cocoa objects are toll- free bridged, meaning they can be used interchangeably. The NSCFArray you’re seeing here is Apple’s implementation of NSArray but using CFArray to do the heavy lifting.
Split and Join Array
To split an NSArray, use -componentsSeparatedByString:, like this:
NSString *string = @"oop:ack:bork:greeble:ponies";
NSArray *chunks = [string componentsSeparatedByString: @":"];
And to join an NSArray and create a string of its contents, use componentsJoinedByString::
string = [chunks componentsJoinedByString: @" :- ) "];
The preceding line would produce an NSString with the contents “oop :- ) ack :- ) bork :- ) greeble :- ) ponies”.
Mutable Arrays
Once you create an array with a certain number of objects, that’s it for that array: you can’t add or remove any members. The objects contained in the array are free to change, of course (like a Car getting a new set of Tires after it fails a safety inspection), but the array object itself will stay the same forever.
NSMutableArray exists so that we can add and remove objects whenever we want. It uses a class method, arrayWithCapacity, to make a new mutable array:
+ (id) arrayWithCapacity: (unsigned) numItems;
NSMutableArray *array;
array = [NSMutableArray arrayWithCapacity: 17];
Add objects to the end of the array by using addObject:.
- (void) addObject: (id) anObject;
for (i = 0; i < 4; i++) {
Tire *tire = [Tire new];
[array addObject: tire];
}
You can remove an object at a particular index.
- (void) removeObjectAtIndex: (unsigned) index;
[array removeObjectAtIndex: 1];
Enumeration Nation
Performing an operation on each element of the array is a common NSArray operation. You can achieve it through:
- You can write a loop to index from 0 to [array count] and get the object at the index
- you can use an NSEnumerator, which is Cocoa’s way of describing this kind of iteration over a collection.
To use NSEnumerator, you ask the array for the enumerator using objectEnumerator:
- (NSEnumerator *) objectEnumerator;
NSEnumerator *enumerator;
enumerator = [array objectEnumerator];
After you get an enumerator, you crank up a while loop that asks the enumerator for its nextObject every time through the loop:
- (id) nextObject;
NSEnumerator *enumerator;
enumerator = [array objectEnumerator];
id thingie;
while (thingie = [enumerator nextObject]) {
NSLog (@"I found %@", thingie);
}
If you’re enumerating over a mutable array: you can’t change the container, such as by adding or removing objects.
Fast Enumeration
In Mac OS X 10.5 (Leopard), Apple introduced fast enumeration:
for (NSString *string in array) {
NSLog (@"I found %@", string);
}
Index vs. Enumarator vs. Fast Enumerator
If you’re only going to be running on Leopard or later OS versions, use fast enumeration.
If you need to support Tiger, go the NSEnumerator way.
Only use -objectAtIndex if you really need to access things by index, like if you’re skipping around the array
NSDictionary
Cocoa has a collection class called NSDictionary. An NSDictionary stores a value (which can be any kind of object) under a given key (usually an NSString).
A dictionary (which is also known as a hash table or an associative array) uses a storage mechanism that’s optimized for key lookups.
NSDictionary, like NSString and NSArray, is an immutable object. However, the NSMutableDictionary class that lets you add and remove stuff at will.
To make a new NSDictionary, you supply all the objects and keys that live in the dictionary at creation time. The easiest way to get started with a dictionary is to use the class method dictionaryWithObjectsAndKeys:.
+ (id) dictionaryWithObjectsAndKeys:
(id) firstObject, ...;
Tire *t1 = [Tire new];
Tire *t2 = [Tire new];
Tire *t3 = [Tire new];
Tire *t4 = [Tire new];
NSDictionary *tires;
tires = [NSDictionary dictionaryWithObjectsAndKeys:
t1, @"front- left", t2, @"front- right",
t3, @"back- left", t4, @"back- right", nil];
To access a value in the dictionary, use the objectForKey: method, giving it the key you previously stored the value under:
// return nil if key is not found
- (id) objectForKey: (id) aKey;
Tire *tire = [tires objectForKey: @"back- right"];
To make a new NSMutableDictionary, send the dictionary message to the NSMutableDictionary class. You can also create a new mutable dictionary and give Cocoa a hint of its eventual size by using dictionaryWithCapacity:
+ (id) dictionaryWithCapacity: (unsigned int) numItems;
- (void) setObject: (id) anObject forKey: (id) aKey;
NSMutableDictionary *tires;
tires = [NSMutableDictionary dictionary];
[tires setObject: t1 forKey: @"front- left"];
[tires setObject: t2 forKey: @"front- right"];
[tires setObject: t3 forKey: @"back- left"];
[tires setObject: t4 forKey: @"back- right"];
If you use setObject:forKey: on a key that’s already there, it replaces the old value with the new one. If you want to take a key out of a mutable dictionary, use the removeObjectForKey: method:
- (void) removeObjectForKey: (id) aKey;
[tires removeObjectForKey: @"back- left"];
Family Values
NSArrays and NSDictionaries hold only objects. They can’t directly contain any primitive types, like int, float, or struct. But you can use objects that embed a primitive value.
NSNumber
Cocoa provides a class called NSNumber that wraps (that is, implements as objects) the primitive numeric types. You can create a new NSNumber using these class methods:
+ (NSNumber *) numberWithChar: (char) value;
+ (NSNumber *) numberWithInt: (int) value;
+ (NSNumber *) numberWithFloat: (float) value;
+ (NSNumber *) numberWithBool: (BOOL) value;
Once you have a primitive type wrapped in an NSNumber, you can get it back out by using one of these instance methods:
- (char) charValue;
- (int) intValue;
- (float) floatValue;
- (BOOL) boolValue;
- (NSString *) stringValue;
NSValue ???
NSNumber is actually a subclass of NSValue, which wraps arbitrary values.
+ (NSValue *) valueWithBytes: (const void *) value
objCType: (const char *) type;
You pass the address of the value you want to wrap (such as an NSSize or your own struct). Usually, you take the address (using the & operator in C) of the variable you want to save. You also supply a string describing the type, usually by reporting the types and sizes of the entries in the struct.
NSNull
We’ve told you that you can’t put nil into a collection, because nil has special meaning to NSArray and NSDictionary. If you want to store empty object, you should use NSNull.
// NSNull hsa only one method
+ (NSNull *) null;
[contact setObject: [NSNull null]
forKey: @"home fax machine"];
id homefax;
homefax = [contact objectForKey: @"home fax machine"];
if (homefax == [NSNull null]) {
// ... no fax machine. rats.
}