Cocoa Programming for Mac OS X - Aaron Hillegass [50]
Key-Value Coding and To-Many Relationships
When designing a class, think of your instance variables as having one of four possible purposes:
1. Simple attributes. Example: Each student has a first name. Simple attributes are typically numbers or instances of NSString, NSDate, or NSData.
2. To-one relationships. Example: Each student has a school. It is like a simple attribute, but the type is a complex object, not a simple one. To-one relationships are implemented using pointers: An instance of Student has a pointer to an instance of School.
3. Ordered to-many relationships. Example: Each playlist has a list of songs. The songs are in a particular order. This is typically implemented using an NSMutableArray.
4. Unordered to-many relationships. Example: Each department has a bunch of employees. You can display the employees in a particular order (such as sorted by last name), but that ordering is not inherent in the relationship. This is typically implemented using an NSMutableSet.
Earlier, we discussed how we could set simple attributes and to-one relationships using key-value coding. Remember that when setting or getting a value for fido, KVC will use the accessors if they exist. Similarly, we can create accessors for ordered and unordered to-many relationships.
Let’s say, for example, that an instance of Playlist has an NSMutableArray of Song objects. If you want to use key-value coding to manipulate that array you will ask the playlist for its mutableArrayValueForKey:. You will get back a proxy object.
That proxy object knows that it represents the array that holds the songs.
id arrayProxy = [playlist mutableArrayValueForKey:@"songs"];
int songCount = [arrayProxy count];
In this example, when asked for the count, the proxy object will ask the Playlist object if it has a countOfSongs method. If Playlist does, it will call the method and return the result. If there is no such method, Playlist will get the array of songs and ask the array for its count (Figure 9.3). Note, then, that naming the method countOfSongs is not just a convention; rather, the key-value coding mechanism goes looking for a method with the right name.
Figure 9.3. Key-Value Coding for Ordered Relationships
There are several cases, so here is a list:
id arrayProxy = [playlist mutableArrayValueForKey:@"songs"];
int x = [arrayProxy count]; // is the same as
int x = [playlist countOfSongs]; // if countOfSongs exists
id y = [arrayProxy objectAtIndex:5] // is the same as
id y = [playlist objectInSongsAtIndex:5]; // if the method exists
[arrayProxy insertObject:p atIndex:4] // is the same as
[playlist insertObject:p inSongsAtIndex:4]; // if the method exists
[arrayProxy removeObjectAtIndex:3] // is the same as
[playlist removeObjectFromSongsAtIndex:3] // if the method exists
There is a similar set of calls for unordered to-many relationships (Figure 9.4).
Figure 9.4. Key-Value Coding for Unordered Relationships
id setProxy = [teacher mutableSetValueForKey:@`students"];
int x = [setProxy count]; // is the same as
int x = [teacher countOfStudents]; // if countOfStudents exists
[setProxy addObject:newStudent]; // is the same as
[teacher addStudentsObject:newStudent]; // if the method exists
[setProxy removeObject:expelledStudent]; // is the same as
[teacher removeStudentsObject:expelledStudent]; // if the method exists
Because we have bound the contentArray of the array controller to the employees array of the RMDocument object, the array controller will use key-value coding to add and remove Person objects. You will take advantage of this to add undo invocations to the undo stack when people are added and removed.
Before we declare these methods in the header file, we must tell the compiler that the Person class exists. In