Last modified on 14 February 2011, at 23:21

Programming Mac OS X with Cocoa for Beginners/Containers - arrays, and dictionarys

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.

ArraysEdit

In Cocoa, NSArray and NSMutableArray objects are 'smarter' than the traditional arrays that you may be used to if you've programmed in C before. C arrays are still available if you need them, but they are very simplistic and in many cases using an NSArray or NSMutableArray will be worthwhile.

Note that an NSArray is a static array (its contents cannot be changed after it is created) while an NSMutableArray is a dynamic array (its contents can be changed after it is created). NSMutableArray is a subclass of NSArray, so it can do everything an NSArray can do and more.

If you want to keep lists of objects, NSMutableArray is generally superior to the equivalent C array. The biggest difference is that an NSMutableArray can grow (and shrink) to accommodate as many objects as you want to store. A C array has a fixed size.

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 basic access methods of NSMutableArray:

- (void)       addObject:(id)object;
- (void)       insertObject:(id)object atIndex:(NSUinteger)index;
- (void)       removeObject:(id)object;
- (void)       removeObjectAtIndex:(NSUinteger)index;
- (id)         objectAtIndex:(NSUInteger)index;
- (NSUInteger) count;

Note that NSUInteger represents an unsigned integer (32 or 64-bit, depending on platform).

We must use these access methods to change what the array stores, there is no special syntax such as myarray[5] to access array elements, as there is with a C array. In some C++ frameworks, the C array syntax can be used to access elements in a dynamic array thanks to operator overloading of the [] operator, but Objective-C does not support this.

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.

DictionariesEdit

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.

IteratorsEdit

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 */
 }

Iterators and enumerators have, to a large extent, been superseded in the Objective-C language by the concept of 'fast enumeration'. Fast enumeration is a language feature that allows you to efficiently and safely enumerate over the contents of a collection using a concise syntax. The Cocoa collection classes support fast enumeration. For example

  NSArray *cities = [NSArray arrayWithObjects:@"London", @"Paris", @"Rome", nil];

  for (NSString *city in cities) // fast enumeration
  {
    NSLog(@"City is %@", city);
  }

Cocoa enumerator classes also support fast enumeration. For example

  NSArray *cities = [NSArray arrayWithObjects:@"London", @"Paris", @"Rome", nil];

  for (NSString *city in [cities reverseObjectEnumerator]) // fast enumeration
  {
    NSLog(@"City is %@", city);
  }

Previous Page: Building a GUI | Next Page: A more ambitious application