Mac OS X Reference Library Apple Developer
Search

Creating a Custom Employee Class

There are three parts to implementing and using a custom Employee class for the application. First, you create the class files and update the managed object model. Second, you implement the accessor method for the derived value, and ensure that key-value observing notifications are sent when any of the value’s components changes. Third, you implement a method to initialize the employee ID for new employees.

The Employee Class

  1. In Xcode, create a custom managed object class using the data modeling tool by performing the following steps:

    1. In Xcode, select the data model and ensure that it is the frontmost editor—for example, simply click inside the model diagram view. (Xcode does not display the Managed Object Class option in the next step unless you do this.)

    2. Select File > New File to show the New File Assistant. In the file type outline view, select Cocoa > Managed Object Class and press Next.

    3. In the subsequent pane select the current project and target, then again press Next.

    4. In the subsequent pane (Managed Object Class Generation), select the Employee entity. Make sure the Generate Accessors and Generate Obj-C 2.0 Properties checkboxes are selected, and that the Generate validation methods checkbox is not selected.

    5. Press Finish. Xcode creates the files for the Employee class.

    Take a moment to look at the files. The attributes and relationships themselves are declared as Objective-C properties and the implementations are specified as dynamic. Similarly, the to-many relationship accessors are declared in a category, but there's no implementation. Core Data dynamically generates the implementations for you at runtime. The declarations provided in the header file make sure you don't get compiler warnings when you invoke the methods. For more details, see Managed Object Accessor Methods in Core Data Programming Guide.

  2. In the data model (use the entity detail pane, or edit the name directly in the Class column in the entity browser), change the class name for the Employee entity from NSManagedObject to Employee.

Support for the Derived Value

The value of fullNameAndID is a concatenation of, and hence dependent on, the values of lastName, firstName, and employeeID. To ensure that the derived value is updated whenever any of its components changes, you must implement a key-value observing method that specifies that a key is dependent on others: you can override keyPathsForValuesAffectingValueForKey:, or more simply (given that in this case there is only a single property that is dependent on other properties) implement keyPathsForValuesAffecting<Key> (see the documentation for keyPathsForValuesAffectingValueForKey: for details).

Steps

  1. In the Employee class’s header file, add a declaration for property:

    @property (nonatomic, readonly) NSString *fullNameAndID;

    Note that this is the only modification that you need to make to the header file. In particular, there is no need to add any instance variables.

  2. In the implementation file, implement the fullNameAndID method. It concatenates the first and last names and the employee ID, as illustrated in the example below.

    - (NSString *)fullNameAndID {
        return [NSString stringWithFormat:@"%@, %@ (%@)",
                self.lastName, self.firstName, self.employeeID];
    }

    Notice how you can invoke the custom accessors directly (here using the dot syntax) even though you haven't provided an implementation.

  3. In the Employee class, implement keyPathsForValuesAffectingFullNameAndID as follows:

    + (NSSet *)keyPathsForValuesAffectingFullNameAndID {
        return [NSSet setWithObjects:
                @"lastName", @"firstName", @"employeeID", nil];
    }
  4. In the nib file, change the contentValues binding for the pop-up menu. Set the model key path to fullNameAndID (the other values remain the same).

You may also add fullNameAndID to the data model as a transient string attribute. (In this case—since the value is solely read-only and dependent on other attributes—there's no functional benefit, but it is worth doing so that the model more fully communicates the entity’s behavior.)

Build and Test

Build and run the application again. You should find that the manager pop-up properly displays the full name and ID of each employee, and that the menu item titles update as you change any of the individual components.

What the steps so far have not addressed, however, is the need to ensure that the value of fullNameAndID is unique when new employees are added.

Initializing the Employee ID

The application could benefit from a means of initializing a managed object when it is created—or more specifically, the first time it is added to the object graph. You shouldn't use the class’s designated initializer—initWithEntity:insertIntoManagedObjectContext:—for this since it's called each time the object is instantiated (when it is first created, and whenever it is subsequently retrieved from the persistent store). Core Data provides a special initialization method, awakeFromInsert, which is called once and only once in the lifetime of a managed object, on the first occasion it is inserted into a managed object context. This may be useful to, for example, set the creation date of a record. Contrast this with awakeFromFetch, which is called on subsequent occasions an object is fetched from a data store.

Implement awakeFromInsert

The following implementation is crude (and should not be used in a production application—the initial tempID reverts to 1 every time the application is launched), it serves, however, to quickly illustrate the principle. You use the primitive accessor method in awakeFromInsert to ensure that the change to the property value is not recorded as a separate undoable action.

- (void)awakeFromInsert {
    static NSInteger tempID = 1;
 
    [super awakeFromInsert];
    self.primitiveEmployeeID = [NSNumber numberWithInteger:tempID++];
}

You also need to declare the primitiveEmployee property in the header file, and specify the implementation as dynamic in the implementation file:

// Employee.h
@property (retain) NSNumber *primitiveEmployeeID;
 
// Employee.m
@dynamic primitiveEmployeeID;

Build and Test

Build and run the application again. You should find that as new employees are added to the document, the employee ID is set, and the ID is incremented for each new employee. More importantly, it is now possible to differentiate between all the employees in the Managers pop-up menu.

What Happened?

Most of the task goals have now been met—primarily by creating a custom class to represent the Employee entity and implementing business logic.

A subtle point here is the interaction between the model and the employees array controller. Recall that you use the array controller to add new employees to the document. When the user interface was created, however, it was not configured to manage instances of a particular class. Instead it was configured to manage an entity (in this case, Employee). When you first built and tested the application, the model specified that employees should be represented by NSManagedObject. When the array controller created a new employee, therefore, it created a new instance of NSManagedObject (and set its entity description accordingly). After you updated the model, however, to specify that employees be represented by Employee, when the array controller created a new employee, it created a new instance of Employee. You will see in principle how this works in the next section when you create an instance of the Department entity.

Code Listing for the Employee Class

The complete listing for the implementation of Employee class up to this point is given in Listing 3-1.

Listing 3-1  Implementation of the Employee class

#import <Cocoa/Cocoa.h>
 
 
@interface Employee : NSManagedObject {
 
}
 
@property (retain) NSNumber *employeeID;
@property (retain) NSNumber *primitiveEmployeeID;
@property (retain) NSString *firstName;
@property (retain) NSString *lastName;
@property (retain) NSNumber *salary;
 
@property (retain) NSManagedObject *department;
@property (retain) NSSet *directReports;
@property (retain) Employee *manager;
 
@property (nonatomic, readonly) NSString *fullNameAndID;
 
 
@end
 
 
@interface Employee (CoreDataGeneratedAccessors)
- (void)addDirectReportsObject:(Employee *)value;
- (void)removeDirectReportsObject:(Employee *)value;
- (void)addDirectReports:(NSSet *)value;
- (void)removeDirectReports:(NSSet *)value;
@end
 
 
 
@implementation Employee
 
@dynamic employeeID;
@dynamic primitiveEmployeeID;
@dynamic firstName;
@dynamic lastName;
@dynamic salary;
@dynamic department;
@dynamic directReports;
@dynamic manager;
 
+ (NSSet *)keyPathsForValuesAffectingFullNameAndID {
    return [NSSet setWithObjects:
            @"lastName", @"firstName", @"employeeID", nil];
}
 
- (void)awakeFromInsert {
    static int tempID = 1;
    self.primitiveEmployeeID = [NSNumber numberWithInteger:tempID++];
}
 
 
- (NSString *)fullNameAndID {
    return [NSString stringWithFormat:@"%@, %@ (%@)",
            self.lastName, self.firstName, self.employeeID];
}
 
@end

Optional Extra—Sorting the Managers Popup

The content of the pop-up button that displays the list of employees is currently unsorted. This can make it difficult to find an employee to set as another's manager. You can ensure that the pop-up menu's content is sorted by creating a sort descriptor to associate with the array controller that manages the collection of managers and rearranging the controller's contents prior to displaying the pop-up.

  1. Add instance variables and outlet properties to the MyDocument class header file for the pop-up button and the managers array controller.

    NSArrayController *managersArrayController;
    NSPopUpButton *managerPopup;
    NSArrayController *employeeTableController;
     
    @property (nonatomic, retain) IBOutlet NSArrayController *managersArrayController;
    @property (nonatomic, retain) IBOutlet NSPopUpButton *managerPopup;
    @property (nonatomic, retain) IBOutlet NSArrayController *employeeTableController;
  2. In the MyDocument class implementation file, synthesize the new properties and release them in the dealloc method.

  3. In Interface Builder, make the connections as appropriate—connect the File's Owner's new managerPopup outlet to the pop-up menu, and the managersArrayController outlet to the Employees array controller that provides the content for the pop-up menu.

  4. In Interface Builder, select the Auto Rearrange Content check box in the attributes inspector for the managers array controller.

  5. In the MyDocument class, implement a windowControllerDidLoadNib: method that sets an array of sort descriptors (actually an array containing a single sort descriptor) for the managers array controller:

    - (void)windowControllerDidLoadNib:(NSWindowController *)windowController {
        [super windowControllerDidLoadNib:windowController];
     
        // Create a sort descriptor to sort on "fullNameAndID"
        NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc]
                initWithKey:@"fullNameAndID" ascending:YES];
     
        // Set the sortDescriptors for the managers array controller
        [managersArrayController setSortDescriptors:[NSArray arrayWithObject:sortDescriptor]];
    }

Now build and test the application. You should find that as you add and edit employees, whenever you activate the managers pop-up button its contents are sorted alphabetically.




Last updated: 2009-02-04

Did this document help you? Yes It's good, but... Not helpful...