iOS Reference Library Apple Developer
Search

The Custom Managed Object Class

The managed object model for this tutorial specifies that the Run entity is represented by a custom class, Run. This chapter shows how to implement the class that uses a scalar value to represent one of its attributes, and how to define custom accessor methods and an initializer that is invoked only when a new instance is first created.

Typically there is no need to add instance variables—it is usually better to let the Core Data framework manage properties—for the purposes of illustration, however, in this example you will use a scalar value for the process ID attribute. You must implement custom accessor methods for any attributes you choose to represent using scalar values.

One drawback with using scalar instance variables is that there is no unambiguous way to represent a nil value. The NSKeyValueCoding protocol defines a special method—setNilValueForKey:—that allows you to specify what happens if an attempt is made to set a scalar value to nil.

There are a number of different situations in which you might want to initialize a managed object. You might want to perform initialization every time an instance of a given class is created, in which case you can simply override the designated initializer. You might also, though, want to perform different initialization whenever an object is retrieved from a persistent store or—perhaps more commonly—only when an object is first created. Core Data provides special methods to cater for both situations—awakeFromFetch and awakeFromInsert respectively. This example illustrates the latter case: You want to record the date and time when a new record is created and not update the value thereafter.

Implementing the Managed Object Subclass

Create the Class Files

The first step is to create the files for the new class. If you had a managed object model as a project resource, you could use the New File assistant to create a managed object class from an entity in the model. In this case, however, you do not, so create the files as you would for any other Objective-C class.

  1. In Xcode, add a new Objective-C class file (.h and .m files) for the Run class.

  2. In the Run.h file, set the class’s superclass to NSManagedObject, and declare properties for date, primitiveDate, and processID (primitiveDate is used in awakeFromInsert—see “Implement the Initializer”). Add an instance variable of type NSInteger for the process ID.

    @interface Run : NSManagedObject {
        NSInteger processID;
    }
    @property (retain) NSDate *date;
    @property (retain) NSDate *primitiveDate;
    @property NSInteger processID;
    @end

Implement the Accessor Methods

Core Data automatically implements accessors for managed object properties at runtime, so typically you don’t have to implement them yourself. When you do, though, (such as for scalar attributes) you must invoke the appropriate access and change notification methods. In the implementation block in the Run.m file, do the following:

  1. Core Data automatically implements accessors for the date attribute at runtime. To suppress compiler warnings, though, declare the date properties as dynamic.

    @dynamic date, primitiveDate;
  2. Implement a get accessor for the process ID. You retrieve the value from the managed object’s instance variable. You invoke the appropriate access notification methods to ensure that if the receiver is a fault, the value is retrieved from the persistent store.

    - (NSInteger)processID {
        [self willAccessValueForKey:@"processID"];
        NSInteger pid = processID;
        [self didAccessValueForKey:@"processID"];
        return pid;
    }
  3. Implement a set accessor for the process ID. You set the value of the managed object’s instance variable. You must also invoke the appropriate change notification methods.

    - (void)setProcessID:(NSInteger)newProcessID {
        [self willChangeValueForKey:@"processID"];
        processID = newProcessID;
        [self didChangeValueForKey:@"processID"];
    }

Dealing With nil Values

If you represent an attribute using a scalar value, you need to specify what happens if the value is set to nil using key-value coding. You do this with the setNilValueForKey: method. In this case, simply set the process ID to 0.

  1. Implement a setNilValueForKey: method. If the key is “processID” then set processID to 0.

    - (void)setNilValueForKey:(NSString *)key {
     
        if ([key isEqualToString:@"processID"]) {
            self.processID = 0;
        }
        else {
            [super setNilValueForKey:key];
        }
    }

Implement the Initializer

NSManagedObject provides a special method—awakeFromInsert—that is invoked only when a new managed object is first created (strictly, when it is inserted into the managed object context) and not when it is subsequently fetched from a persistent store. You can use it here to record the date and time when a new record is created (the value won’t then be updated when an object is fetched).

  1. Implement an awakeFromInsert method that sets the receiver’s date to the current date and time.

    - (void) awakeFromInsert {
        [super awakeFromInsert];
        self.primitiveDate = [NSDate date];
    }

You use the primitive accessor in awakeFromInsert to change to the date. The primitive accessors do not emit KVO notifications that cause the change to be recorded as a separate undo event.

Create an Instance of the Run Entity

To create a new instance of a given entity and insert it into a managed object context, you usually use the NSEntityDescription convenience method insertNewObjectForEntityForName:inManagedObjectContext:. The advantage of using the convenience method is that it’s convenient! In this case, though, you’ll perform the set-up operations yourself. Given the new instance, you can set its process ID to the ID of the current process, then send the managed object context a save message to commit the change to the persistent store.

  1. In the main source file, import the header for the Run class.

    #import "Run.h"
  2. In the main function, after the invocation of the managedObjectContext() function, create a new instance of the Run class. You must retrieve the Run entity description from the managed object model so that you can tell the new managed object of what entity it is an instance.

    NSEntityDescription *runEntity = [[mom entitiesByName] objectForKey:@"Run"];
    Run *run = [[Run alloc] initWithEntity:runEntity insertIntoManagedObjectContext:moc];
  3. Get the process ID of the current process, and set the process ID of the Run object.

    NSProcessInfo *processInfo = [NSProcessInfo processInfo];
    run.processID = [processInfo processIdentifier];
  4. Commit the changes to the persistent store by saving the managed object context. Check for any errors, and exit if an error occurs.

    NSError *error = nil;
     
    if (![moc save: &error]) {
        NSLog(@"Error while saving\n%@",
            ([error localizedDescription] != nil) ? [error localizedDescription] : @"Unknown Error");
        exit(1);
    }

Build and Test

Build and run the utility. It should compile without warnings. When you run the utility, it should not log any errors. You should see a new file created in the application log directory. If you inspect the file, you should see that it contains details of run objects.

Test some of the other features. Comment out the line that sets the Run object’s process ID. Build and run the utility. What happens (recall that the default value for the process ID is -1)? Do you see the localized error message (defined in “Add a Localization Dictionary”)? Use key-value coding to set the process ID to nil. Build and run the utility. Again, what happens? And finally, comment out the setNilValueForKey: method and test once more.

Complete Listings

The Run Class

A complete listing of the declaration and implementation of the Run class is shown in Listing 6-1.

Listing 6-1  Complete listing of the declaration and implementation of the Run class

@interface Run : NSManagedObject
{
    NSInteger processID;
}
 
@property (retain) NSDate *date;
@property (retain) NSDate *primitiveDate;
@property NSInteger processID;
 
@end
 
 
@implementation Run
 
@dynamic date, primitiveDate;
 
- (void) awakeFromInsert{
    // set date to now
    self.primitiveDate = [NSDate date];
}
 
 
- (NSInteger)processID {
    [self willAccessValueForKey:@"processID"];
    NSInteger pid = processID;
    [self didAccessValueForKey:@"processID"];
    return pid;
}
 
 
- (void)setProcessID:(NSInteger)newProcessID {
    [self willChangeValueForKey:@"processID"];
    processID = newProcessID;
    [self didChangeValueForKey:@"processID"];
}
 
 
- (void)setNilValueForKey:(NSString *)key {
    if ([key isEqualToString:@"processID"]) {
        self.processID = 0;
    }
    else {
        [super setNilValueForKey:key];
    }
}
 
@end

The main() Function

The main function is shown in Listing 6-2.

Listing 6-2  Listing of the main function

int main (int argc, const char * argv[]) {
 
    objc_startCollectorThread();
 
    NSManagedObjectModel *mom = managedObjectModel();
    NSLog(@"mom: %@", mom);
 
    if (applicationLogDirectory() == nil) {
        NSLog(@"Could not find application logs directory\nExiting...");
        exit(1);
    }
 
    NSManagedObjectContext *moc = managedObjectContext();
 
    NSEntityDescription *runEntity = [[mom entitiesByName] objectForKey:@"Run"];
    Run *run = [[Run alloc] initWithEntity:runEntity insertIntoManagedObjectContext:moc];
 
    NSProcessInfo *processInfo = [NSProcessInfo processInfo];
    run.processID = [processInfo processIdentifier];
 
    NSError *error = nil;
 
    if (![moc save: &error]) {
        NSLog(@"Error while saving\n%@",
            ([error localizedDescription] != nil) ? [error localizedDescription] : @"Unknown Error");
        exit(1);
    }
 
    // Implementation will continue...
 
    return 0;
}



Last updated: 2010-05-24

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