< Previous PageNext Page > Hide TOC

Using a Managed Object Model

This article describes how you use a managed object model in your application.

Contents:

Creating and Loading a Managed Object Model
Changing a Model
Accessing and Using a Managed Object Model at Runtime
Localizing a Managed Object Model


Creating and Loading a Managed Object Model

You usually create a model in Xcode, as described in Creating a Managed Object Model with Xcode. You can also create a model entirely in code, as show in Listing 3 and described in Core Data Utility Tutorial—typically, however, this is too long-winded to consider in anything but the most trivial application. (You are nevertheless encouraged to review the tutorial to gain an understanding of what the modeling tool does, and in particular to gain an appreciation that the model is simply a collection of objects.)

Compiling a Data Model

A data model is a deployment resource. In addition to details of the entities and properties in the model, a model you create in Xcode contains information about the diagram—its layout, colors of elements, and so on. This latter information is not needed at runtime. The model file is compiled to remove the extraneous information and make runtime loading of the resource as efficient as possible. The xcdatamodel "source" file is compiled into a mom deployment file using the model compiler, momc.

momc is located in /Library/Application Support/Apple/Developer Tools/Plug-ins/XDCoreDataModel.xdplugin/Contents/Resources/. If you want to use it in your own build scripts, its usage is momc source destination, where source is the path of the Core Data model to compile and destination is the path of the output mom file.

Loading a Data Model

In many cases, you do not have to write any code to load a model. If you use a document-based application, NSPersistentDocument manages the task of finding and loading your application's model for you. If you use the Core Data Application template, the application delegate includes code to retrieve the model. Note that the name of a model—as represented by the filename used to store it on disk—is not relevant at runtime. Once the model is loaded by Core Data, the filename is meaningless and has no use, so you can name the model file whatever you like.

If you want to load a model yourself, there are two mechanisms you can use, each with its own benefits:

The class method is useful in cases where segregation of models is not important—for example, you may know your application and a framework it links to both have models you need or want to load. The class method allows you to easily load all of the models at once without having to consider what the names are, or put in specialized initialization code to ensure all of your models are found

In cases where you have more than one model, however—and particularly in cases where the models represent different versions of the same schema—knowing which model to load is essential (merging together models with the same entities at runtime into a single collection would cause naming collisions and errors). In these situations, you use the instance method. Additionally, there may be situations when you want to store the model outside of the bundle for your application, thus requiring the need to reference it via a file-system URL.

Note there is also a class method, modelByMergingModels:, which merges a given array of models much like the mergedModelFromBundles: method does. Thus, you can still load individual models via URLs and then unify them before instantiating a coordinator with them.

Changing a Model

Since a model describes the structure of the data in a persistent store, changing any parts of a model that alters the schema renders it incompatible with (and so unable to open) the stores it previously created. If you change your schema, you therefore need to migrate the data in existing stores to new version (see “Mac OS X v10.4: Versioning”). For example, if you add a new entity or a new attribute to an existing entity, you will not be able to open old stores; if you add a validation constraint or set a new default value for an attribute, you will be able to open old stores.

Accessing and Using a Managed Object Model at Runtime

It is important to realize that, at runtime, a managed object model is simply a graph of objects. This knowledge is especially useful if you need to gain access to details of the model programmatically. You might need to do this either to modify the model (you can do this only before it is used at runtime, see NSManagedObjectModel), or to retrieve information such as a localized entity name, the data type of an attribute, or a fetch request template.

There are a number of ways you can access a managed object model at runtime. Through the persistence stack you ultimately get the model from the persistent store coordinator. Thus to get the model from a managed object context, you use the following code:

[[aManagedObjectContext persistentStoreCoordinator] managedObjectModel];

You can also retrieve the model from an entity description, so given a managed object you can retrieve its entity description and hence the model, as shown in the following example.

[[aManagedObject entity] managedObjectModel];

In some cases, you maintain a "direct" reference to the model—that is, a method that returns the model directly. NSPersistentDocument provides managedObjectModel that returns the model associated with the persistent store coordinator used by the document's managed object context. If you use the Core Data Application template, the application delegate maintains a reference to the model.

Creating Fetch Request Templates Programmatically

You can create fetch request templates programmatically and associate them with a model using setFetchRequestTemplate:forName: as illustrated in Listing 1. Recall, though, that you can only modify the model before it has been used by a store coordinator.

Listing 1  Creating a fetch request template programmatically

NSManagedObjectModel *model = ...;
NSFetchRequest *requestTemplate = [[NSFetchRequest alloc] init];
NSEntityDescription *publicationEntity =
    [[model entitiesByName] objectForKey:@"Publication"];
[requestTemplate setEntity:publicationEntity];
 
NSPredicate *predicateTemplate = [NSPredicate predicateWithFormat:
    @"(mainAuthor.firstName like[cd] $FIRST_NAME) AND \
        (mainAuthor.lastName like[cd] $LAST_NAME) AND \
        (publicationDate > $DATE)"];
[requestTemplate setPredicate:predicateTemplate];
 
[model setFetchRequestTemplate:requestTemplate
    forName:@"PublicationsForAuthorSinceDate"];
[requestTemplate release];

Accessing Fetch Request Templates

You can retrieve and use a fetch request template as illustrated in the code fragment in “Accessing and Using a Managed Object Model at Runtime.” The substitution dictionary must contain keys for all the variables defined in the template; if you want to test for a null value, you must use an NSNull object—see Using Predicates.

Listing 2  Using a fetch request template

NSManagedObjectModel *model = ...;
NSError *error = nil;
NSDictionary *substitutionDictionary = [NSDictionary dictionaryWithObjectsAndKeys:
    @"Fiona", @"FIRST_NAME", @"Verde", @"LAST_NAME",
    [NSDate dateWithTimeIntervalSinceNow:-31356000], @"DATE", nil];
NSFetchRequest *fetchRequest =
    [model fetchRequestFromTemplateWithName:@"PublicationsForAuthorSinceDate"
            substitutionVariables:substitutionDictionary];
NSArray *results =
    [aManagedObjectContext executeFetchRequest:fetchRequest error:&error];

If the template does not have substitution variables, you must either:

  1. Use fetchRequestFromTemplateWithName:substitutionVariables: and pass nil as the variables argument; or

  2. Use fetchRequestTemplateForName: and copy the result.

    If you try to use the fetch request returned by fetchRequestTemplateForName:, this generates an exception ("Can't modify a named fetch request in an immutable model").

Localizing a Managed Object Model

You can localize most aspects of a managed object model, including entity and property names and error messages. It is important to consider that localization also includes "localization into your own language." Even if you do not plan to provide foreign-language versions of your application, you can provide a better experience for your users if error messages show "natural language" names rather than "computer language" names (for example, "First Name is a required property" rather than "firstName is a required property").

You localize a model by providing a localization dictionary that follows the pattern shown in the table below.

Table 1  Keys and values in a localization dictionary for a managed object model

Key

Value

Note

"Entity/NonLocalizedEntityName"

"LocalizedEntityName"

"Property/NonLocalizedPropertyName/Entity/EntityName"

"LocalizedPropertyName"

1

"Property/NonLocalizedPropertyName"

"LocalizedPropertyName"

"ErrorString/NonLocalizedErrorString"

"LocalizedErrorString"

Note: (1) For properties in different entities with the same non-localized name but which should have different localized names.

You can access the localization dictionary using the method localizationDictionary. Note, however, that in the implementation in Mac OS X version 10.4, localizationDictionary may return nil until Core Data lazily loads the dictionary for its own purposes (for example, reporting a localized error).

Strings File

The easiest way to localize a model is to create a corresponding strings file—the strings file name is the same as the model file name, but with a .strings rather than a .xcdatamodel extension (for example, for a model file named MyDocument.xcdatamodel the corresponding strings file is MyDocumentModel.strings—if your model file name already includes the suffix "Model", you must append a further "Model", so the strings file corresponding to JimsModel.xcdatamodel would be the rather unlikely-looking JimsModelModel.strings). The file format is similar to a standard strings file you use for localization (see Strings Files) but the key and value pattern follows that shown in Table 1.

A strings file for a model that includes an employee entity might contain the following:

"Entity/Emp" = "Employee";
"Property/firstName" = "First Name";
"Property/lastName" = "Last Name";
"Property/salary" = "Salary";

A further example is given in NSPersistentDocument Core Data Tutorial.

Setting a Localization Dictionary Programmatically

You can set a localization dictionary at runtime using the NSManagedObjectModel method setLocalizationDictionary:. You must create a dictionary with keys and values as shown in Table 1, and associate it with the model. You must ensure you do this before the model is used to fetch or create managed objects, as the model is uneditable thereafter. The listing shown in Listing 3 illustrates the creation in code of a managed object model including a localization dictionary. The entity is named "Run" and is represented at runtime by the Run class. The entity has two attributes, "date" and "processID"—a date and an integer respectively. The process ID has a constraint that its value must not be less than zero.

Listing 3  Creating a managed object model in code

NSManagedObjectModel *mom = [[NSManagedObjectModel alloc] init];
NSEntityDescription *runEntity = [[NSEntityDescription alloc] init];
[runEntity setName:@"Run"];
[runEntity setManagedObjectClassName:@"Run"];
[mom setEntities:[NSArray arrayWithObject:runEntity]];
[runEntity release];
 
NSMutableArray *runProperties = [NSMutableArray array];
 
NSAttributeDescription *dateAttribute = [[NSAttributeDescription alloc] init];
[runProperties addObject:dateAttribute];
[dateAttribute release];
[dateAttribute setName:@"date"];
[dateAttribute setAttributeType:NSDateAttributeType];
[dateAttribute setOptional:NO];
 
NSAttributeDescription *idAttribute= [[NSAttributeDescription alloc] init];
[runProperties addObject:idAttribute];
[idAttribute release];
[idAttribute setName:@"processID"];
[idAttribute setAttributeType:NSInteger32AttributeType];
[idAttribute setOptional:NO];
[idAttribute setDefaultValue:[NSNumber numberWithInt:0]];
 
NSPredicate *validationPredicate = [NSPredicate predicateWithFormat:@"SELF >= 0"];
NSString *validationWarning = @"Process ID < 0";
[idAttribute setValidationPredicates:[NSArray arrayWithObject:validationPredicate]
    withValidationWarnings:[NSArray arrayWithObject:validationWarning]];
 
[runEntity setProperties:runProperties];
 
NSMutableDictionary *localizationDictionary = [NSMutableDictionary dictionary];
[localizationDictionary setObject:@"Process ID"
    forKey:@"Property/processID/Entity/Run"];
[localizationDictionary setObject:@"Date"
    forKey:@"Property/date/Entity/Run"];
[localizationDictionary setObject:@"Process ID must not be less than 0"
    forKey:@"ErrorString/Process ID < 0"];
[mom setLocalizationDictionary:localizationDictionary];


< Previous PageNext Page > Hide TOC


© 2004, 2009 Apple Inc. All Rights Reserved. (Last updated: 2009-03-04)


Did this document help you?
Yes: Tell us what works for you.
It’s good, but: Report typos, inaccuracies, and so forth.
It wasn’t helpful: Tell us what would have helped.