Programming Mac OS X with Cocoa for beginners/Containers - arrays, and dictionarys
From Wikibooks, the open-content textbooks collection
Previous Page: Building a GUI | Next Page: A more ambitious application
We have already had a brief encounter with NSDictionary, when we used one to set up a couple of attributes for drawing our "Hello World" string. Now we will have a look at it in more detail, and look at some of Cocoa's other container classes.
A container class is an object that is designed simply to look after collections of other objects. Time and again we will need to keep things in lists or tables, and the two main classes in Cocoa that do this job are NSArray and NSDictionary, and their mutable versions, NSMutableArray and NSMutableDictionary. In addition, Cocoa has some other containers such as NSSet, but for this tutorial we will not be looking at these.
[edit] Arrays
In Cocoa, the NSArray object is far smarter than the plain C array that you may be used to if you've programmed in C before. C arrays are still available if you need one, but since they are very simplistic, in most cases using an NSArray will be worthwhile. If you want to keep lists of objects, NSArray is infinitely superior to a C array.
The first difference is that an NSArray can grow to accommodate as many objects as you want to store. A C array is a fixed size. However, just like a C array, you refer to the elements of the array using a simple index, starting at 0 for the first element. Here are the main basic methods of NSMutableArray:
- (void) addObject:(id) object; - (void) insertObject:(id) object atIndex:(int) index; - (void) removeObject:(id) object; - (id) objectAtIndex:(int) index; - (int) count;
We must use these methods to change what the array stores, there is no special syntax to access array elements, as there is with a C array. In some C++ frameworks, the C syntax can be used to access dynamic array objects thanks to operator overloading, but Objective-C doesn't support that, so we just use the ordinary methods that we already know about.
addObject: adds an object to the end of the array. insertObject:atIndex: inserts an object anywhere in the array, though be sure that the index already exists - if it's beyond the end of the array you'll get an error. Elements above the index inserted will be moved up one to accommodate the new item. removeObject: removes an object from the array, moving elements at higher index positions down to fill the gap. objectAtIndex: returns the object stored at the index, and count returns the total number of elements in the array.
When objects are added to an array, the array retains the object. When the object is removed from the array, the array releases the object. This means that objects within an array should always be valid. If the array itself is released, all the objects it contains are released too. All of Cocoa's object containers work this way.
[edit] Dictionaries
An array is great when you want to use index numbers to refer to objects, and don't mind if the indexes can change as objects come and go. You can use arrays for other types of structures too, such as queues and stacks. However, when you want to store objects in a way so that they are always associated with the same "index value", or key, a dictionary is a better bet.
NSDictionary and NSMutableDictionary are Cocoa's dictionary classes. Instead of an index, you associate objects with a key value. Later, you can retrieve the object using the key. The key never changes even as other objects come and go.
The main methods are:
- (void) setObject:(id) object forKey:(id) key; - (id) objectForKey:(id) key; - (void) removeObjectForKey:(id) key; - (int) count;
The key is itself declared to be an object, so you can use anything you like. Most commonly, you would use an NSString as the key, but you are not required to.
Internally, NSDictionary is implemented as a hash table, so accessing elements is fast.
[edit] Iterators
Often, you will want to loop over the contents of an array or dictionary, and carry out some action on each object. Cocoa declares a number of iterator or enumerator classes to make this easy. Of course, you could use a simple for(...) loop, using 'count' as a terminal value, but using an iterator is both more object-oriented, and allows quick and easy change between using an array or a dictionary for storage.
To obtain an iterator for a given container, ask the container to make it for you. The generic iterator class is NSEnumerator:
NSEnumerator* myIterator = [myArray objectEnumerator];
Because 'objectEnumerator' is a factory method, the returned iterator is autoreleased, so you don't usually need to retain it - you'll be using it once to iterate the contents of the array, then throw it away. In fact, iterators can only be used once - once you have iterated the array, there is no way to "back up" and do it again with the same iterator. Instead, you ask for a new iterator, which starts at the beginning.
To iterate, you repeatedly call the iterator's 'nextObject' method until it returns nil, meaning there are no more objects. So, an iterative loop looks like:
id anObject;
while( anObject = [myIterator nextObject])
{
/* do something useful with anObject */
}
This code would be identical whether the container was a dictionary or an array. If you used a for loop, the dictionary case would need to be quite a bit more complex than the array case, since you'd need to obtain all of the keys, then get each in turn to obtain the object.
If you wish to iterate backwards over a set of objects, ask for the reverseObjectEnumerator instead. The body of the loop doesn't change:
NSEnumerator* myIterator = [myArray reverseObjectEnumerator];
id anObject;
while( anObject = [myIterator nextObject])
{
/* do something useful with anObject */
}
Previous Page: Building a GUI | Next Page: A more ambitious application