Creating the Managed Object Model

This chapter specifies the Run entity and shows you how to create the managed object model. Although it is typically easiest to create the model in Xcode, in this tutorial you create the model entirely in code.

Xcode has a data modeling tool that you typically use to define the schema for your application (see Xcode Tools for Core Data for full details). The Xcode data modeling tool is analogous to Interface Builder in that it allows you to graphically create a complex graph of objects that is archived and at runtime is unarchived. Creating a user interface without Interface Builder is possible, but can require a lot of effort. Similarly, even a reasonably straightforward model requires a lot of code, so this tutorial only uses a single entity with two simple attributes.

Specifying the Entity

The Run entity has two attributes, the process ID and the date on which the process was run. Neither attribute is optional—that is, each must have a value if an instance is to be considered valid (and if you try to save an instance without a value, you will get a validation error). The process ID has a default value of -1. In conjunction with the validation rules, this ensures that the value is properly set at runtime. You must also specify the class that will represent the entity in the utility—in this example you will use a custom class named “Run”.

Table 2-1  Attributes for the Run entity

Name

Type

Optional

Default Value

Minimum Value

date

date

NO

processID

int

NO

-1

0

Create the Managed Object Model

You could create the model in Xcode, put it in the application support directory, and load it at runtime using NSManagedObjectModel’s initWithContentsOfURL:. This example, however, illustrates how to create the model entirely in code. The managedObjectModel function creates the Run entity and its associated attributes, then creates a managed object model instance and adds the Run entity to it. For a additional layer of polish, it also adds a localization dictionary to the model—using a localization dictionary means that any log messages associated with the model are typically easier to understand.

Declare and Implement the managedObjectModel Function

The managedObjectModel function is used in the main function, but implemented after the main function; you should therefore provide a forward declaration of the managedObjectModel function.

bullet
To declare the managedObjectModel function
  • At the top of the main source file, before the main function, add a declaration for the function.

    NSManagedObjectModel *managedObjectModel();

The next step is to declare and create a stub implementation of the managedObjectModel() function. The function should determine whether the managed object context instance already exists. If it does, simply return it, if it doesn’t, create it and then configure the remainder of the stack.

bullet
To begin the implementation of the managedObjectModel function
  • In the main source file, begin the implementation of the managedObjectModel function.

    The function declares a static variable (mom) for the model itself, and returns the variable immediately if it is not nil. The function should ultimately return mom—the next steps will create the model and the entity it contains.

    Add the following code:

    NSManagedObjectModel *managedObjectModel() {
     
        static NSManagedObjectModel *mom = nil;
     
        if (mom != nil) {
            return mom;
        }
     
        // Implementation continues...
        return mom;
    }

You should enter the code described in the following sections (Create the Run Entity, Add the Attributes, Create the Model, and Add a Localization Dictionary) immediately before the return statement (where the comment states, “Implementation continues...”).

Create the Run Entity

The first step of creating the model is to create the Run entity, represented by an instance of NSEntityDescription. The entity description specifies both the name of the entity and the name of the class that will be used to represent instances of the entity at runtime. In this case, both names are “Run”.

bullet
To create the Run entity
  • Create the entity description object, set its name and managed object class name, and add it to the model.

    Add the following code:

    NSEntityDescription *runEntity = [[NSEntityDescription alloc] init];
    [runEntity setName:@"Run"];
    [runEntity setManagedObjectClassName:@"Run"];

Add the Attributes

Attributes are represented by instances of NSAttributeDescription. You must create two instances—one for the date, the other for the process ID—and set their characteristics appropriately. Both require a name and a type, and neither is optional. The process ID has a default value of -1. You also need to create a predicate for the process ID validation.

bullet
To add attributes to the Run entity
  1. Create the date attribute description—its type is NSDateAttributeType and it is not optional.

    Add the following code:

    NSAttributeDescription *dateAttribute = [[NSAttributeDescription alloc] init];
     
    [dateAttribute setName:@"date"];
    [dateAttribute setAttributeType:NSDateAttributeType];
    [dateAttribute setOptional:NO];
  2. Create the process ID attribute description—its type is NSInteger64AttributeType, it is not optional, and its default value is -1.

    Add the following code:

    NSAttributeDescription *idAttribute = [[NSAttributeDescription alloc] init];
     
    [idAttribute setName:@"processID"];
    [idAttribute setAttributeType:NSInteger64AttributeType];
    [idAttribute setOptional:NO];
    [idAttribute setDefaultValue:@(-1)];
  3. Create the validation predicate for the process ID; the value of the attribute itself must be greater than zero.

    The following code is equivalent to validationPredicate = [NSPredicate predicateWithFormat:@"SELF > 0"], but this example continues the theme of illustrating the long-hand form.

    Add the following code:

    NSExpression *lhs = [NSExpression expressionForEvaluatedObject];
    NSExpression *rhs = [NSExpression expressionForConstantValue:@0];
     
    NSPredicate *validationPredicate = [NSComparisonPredicate
                                            predicateWithLeftExpression:lhs
                                            rightExpression:rhs
                                            modifier:NSDirectPredicateModifier
                                            type:NSGreaterThanPredicateOperatorType
                                            options:0];
  4. Add error strings for the validation predicates.

    Each validation predicate requires a corresponding error string. Typically the error string should be appropriately localized. You can either provide a localized representation here (using, for example, NSLocalizedString) or supply a localization dictionary for the model. The latter is shown in the next section (Add a Localization Dictionary). You provide the attribute description with an array of predicates and an array of error strings. In this case, each array contains just a single object.

    Add the following code:

    NSString *validationWarning = @"Process ID < 1";
    [idAttribute setValidationPredicates:@[validationPredicate]
            withValidationWarnings:@[validationWarning]];
  5. Add the properties to the entity.

    Add the following code:

    [runEntity setProperties:@[dateAttribute, idAttribute]];

Create the Model

You next create the managed object model itself and add the Run entity to it. A model can contain more than one entity, so the method to set the entities takes an array of entities.

bullet
To create the model
  • Create the model, and add the Run entity to the model in an array.

    Add the following code:

    mom = [[NSManagedObjectModel alloc] init];
    [mom setEntities:@[runEntity]];

Add a Localization Dictionary

You can set a localization dictionary to provide localized string values for entities, properties, and error strings related to the model. The key and value pattern is described in the API reference for setLocalizationDictionary:. The string you use as the key for the error must be the same as that you specified for the corresponding validation predicate.

bullet
To add a localization dictionary to the model
  • Create the localization dictionary and set it for the model:

    Add the following code:

    NSDictionary *localizationDictionary = @{
                            @"Property/date/Entity/Run":@"Date",
                            @"Property/processID/Entity/Run":@"Process ID",
                            @"ErrorString/Process ID < 1":@"Process ID must not be less than 1"};
     
    [mom setLocalizationDictionary:localizationDictionary];

Test Your Code

So that you can test the implementation thus far, instantiate the managed object model and log its description of the model.

bullet
To test your implementation so far
  1. Create an instance of the managed object model and log its description.

    In the main function, within the @autoreleasepool block, declare a variable of class NSManagedObjectModel and assign its value to the result of invoking the managedObjectModel function. Print the model description using NSLog.

    NSManagedObjectModel *mom = managedObjectModel();
    NSLog(@"The managed object model is defined as follows:\n%@", mom);
  2. Build and run the project.

    The project should compile without warnings. The logged description of the model file should contain the entity and attributes you defined. At this stage the model has not yet been used, so its isEditable state remains true.

Complete Listing

The complete listing of the managedObjectModel function is shown in Listing 2-1.

Listing 2-1  Complete listing of the managedObjectModel function

NSManagedObjectModel *managedObjectModel() {
 
    static NSManagedObjectModel *mom = nil;
 
    if (mom != nil) {
        return mom;
    }
 
    NSEntityDescription *runEntity = [[NSEntityDescription alloc] init];
    [runEntity setName:@"Run"];
    [runEntity setManagedObjectClassName:@"Run"];
 
    NSAttributeDescription *dateAttribute = [[NSAttributeDescription alloc] init];
 
    [dateAttribute setName:@"date"];
    [dateAttribute setAttributeType:NSDateAttributeType];
    [dateAttribute setOptional:NO];
 
 
    NSAttributeDescription *idAttribute = [[NSAttributeDescription alloc] init];
 
    [idAttribute setName:@"processID"];
    [idAttribute setAttributeType:NSInteger64AttributeType];
    [idAttribute setOptional:NO];
    [idAttribute setDefaultValue:@(-1)];
 
    NSExpression *lhs = [NSExpression expressionForEvaluatedObject];
    NSExpression *rhs = [NSExpression expressionForConstantValue:@0];
 
    NSPredicate *validationPredicate = [NSComparisonPredicate
                                            predicateWithLeftExpression:lhs
                                            rightExpression:rhs
                                            modifier:NSDirectPredicateModifier
                                            type:NSGreaterThanPredicateOperatorType
                                            options:0];
 
    NSString *validationWarning = @"Process ID < 1";
 
    [idAttribute setValidationPredicates:@[validationPredicate]
                 withValidationWarnings:@[validationWarning]];
 
    [runEntity setProperties:@[dateAttribute, idAttribute]];
 
    mom = [[NSManagedObjectModel alloc] init];
    [mom setEntities:@[runEntity]];
 
    NSDictionary *localizationDictionary = @{
                            @"Property/date/Entity/Run":@"Date",
                            @"Property/processID/Entity/Run":@"Process ID",
                            @"ErrorString/Process ID < 1":@"Process ID must not be less than 1"};
 
    [mom setLocalizationDictionary:localizationDictionary];
 
    return mom;
}