Programmatically using CoreData

by Thizzer

Apple’s CoreData provides an easy-to-use mechanism to store data and objects in your iOS and OSX Apps. Using the .xcdatamodeld file you can predefine your model and using that you can generate NSManagedObject subclasses to allow easy interaction with your database. CoreData also allows for versioning of your datamodel so that you have to do the least manual conversion possible when making changes to it.

 

But there is another way to do this. Programmatically. This means we are not defining the datamodel in the .xcdatamodeld file but in our code.

 

In this article I will explain how to programatically create and manage CoreData storage in Objective-C. Later I will also try to add a Swift variant.

1 · Creating the NSManagedObjectModel

First we need to create a NSManagedObjectModel in which we store the information about any of the objects we wish to store. This ‘model’ described the schema that we are going to use.

 

To prevent having to recreate our model each time our application starts we can store the model. Because NSManagedObjectModel conforms to NSCoding we can store it in a file using the NSKeyedArchiver and read it from this file using the NSKeyedUnarchiver.

 

First we define the location where we want to store the model, because we want the model to be persistent we store it in the documents-directory:

 

NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];

// The datamodel will be stored in the documents-directory in a ‘db’ subdirectory
NSString *nameForModel = @"AppDataModel.momd";
NSString *pathToModel = [documentsDirectory stringByAppendingFormat:@"/db/%@", nameForModel];

 

Next we check to see if the model has already been created and just needs to be loaded or that it needs to be created.

 

NSManagedObjectModel *objectModel = nil;
if([[NSFileManager defaultManager] fileExistsAtPath:pathToModel])
{
	// there is a file present that should contain the model
	NSData *dataForModel = [NSData dataWithContentsOfFile:pathToModel];
	NSManagedObjectModel  *objectModel = [NSKeyedUnarchiver unarchiveObjectWithData:dataForModel];
}

 

If the model has not yet been created or we we not able to load it from the file we have to initialise it.

 

if(objectModel == nil)
{
	objectModel = [[NSManagedObjectModel alloc] init];
}

 

At this point we have either loaded an existing NSManagedObjectModel or we have initialised a new one. For the latter we now have to add the entities we actually want to store and initialise the NSPersistentStoreCoordinator and the NSManagedObjectContext to be able to communicate with the storage.

 

2 · Adding Entities

Next we are going to add entities to the model which represent the kind of data we want to store. Let say we want to store several types of rated coffee. Our Coffee-model is represented as follows

 

Field Type
name NSString
brand NSString
rating NSNumber

 

First we will create an array which will contain the NSEntityDescription for coffee which we are going to add to the model. Also we create an array for the attributes of coffee. These are simple operations but for the sake of completion I will add an example.

 

NSMutableArray *entities = [[NSMutableArray alloc] init];
NSMutableArray *coffeeAttributes = [[NSMutableArray alloc] init];

 

we initialise the NSEntityDescription for coffee

 

NSEntityDescription *coffeeEntity = [[NSEntityDescription alloc] init];
[coffeeEntity setName:@"Coffee"];

 

and create all the required NSAttributeDescriptions containing information about the attributes of coffee.

 

NSAttributeDescription *nameAttribute = [[NSAttributeDescription alloc] init];
[nameAttribute setName:@"name"];
[nameAttribute setAttributeType:NSStringAttributeType];
[nameAttribute setOptional:NO];
[nameAttribute setIndexed:YES]; // set this to no if you are not going to search this attribute

NSAttributeDescription *brandAttribute = [[NSAttributeDescription alloc] init];
[brandAttribute setName:@"brand"];
[brandAttribute setAttributeType:NSStringAttributeType];
[brandAttribute setOptional:NO];
[brandAttribute setIndexed:YES]; // set this to no if you are not going to search this attribute

NSAttributeDescription *ratingAttribute = [[NSAttributeDescription alloc] init];
[ratingAttribute setName:@"rating"];
[ratingAttribute setAttributeType:NSDecimalAttributeType];
[ratingAttribute setOptional:YES];
[ratingAttribute setIndexed:NO]; // set this to yes if you are going to search this attribute

[coffeeAttributes addObject:nameAttribute];
[coffeeAttributes addObject:brandAttribute];
[coffeeAttributes addObject:ratingAttribute];

 

Once you have described all the attributes we can add them to the NSEntityDescription.

 

[coffeeEntity setProperties:coffeeAttributes];

[entities addObject:coffeeEntity];
[objectModel setEntities:entities];

 

The entity has now been added to the ObjectModel. If you want to store more than one entity using CoreData you can create multiple NSEntityDescriptions and add these to the ‘entities’ array before calling setEntities on the object-model.

 

If in the future you would like to add new entities to the NSManagedObjectModel you will have to merge the current ‘version’ with a new one. We will discuss this in an upcoming article ‘Merging programatically created NSManagedObjectModel’.

 

3 · Save the NSManagedObjectModel

As previously stated; to prevent having to recreate our model each time our application starts we can store the model. Because NSManagedObjectModel conforms to NSCoding we can store it in a file using the NSKeyedArchiver as follows.

 

NSData *modelData = [NSKeyedArchiver archivedDataWithRootObject:objectModel];
[modelData writeToFile:pathToModel atomically:YES];

 

4 · Creating NSPersistentStoreCoordinator and initialising NSManagedObjectContext

To be able to communicate with the actual data-storage we have to set-up the NSPersistentStoreCoordinator and NSManagedObjectContext. Use the following code to achieve this

 

NSPersistentStoreCoordinator *persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:objectModel];

NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];

// The store will be stored in the documents-directory in a db subdirectory
NSString *nameForSQLiteStore = @"AppDataDb.sqlite";
NSString *pathToSQLiteStore = [documentsDirectory stringByAppendingFormat:@"/db/%@", nameForSQLiteStore];

// !important - use PF_DEFAULT_CONFIGURATION_NAME so CoreData handles the stor properly
if(![_persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:@"PF_DEFAULT_CONFIGURATION_NAME" URL:pathToSQLiteStore options:nil error:&error])
{
// An error has occurred - Most of the time you have to recreate the model and consider your data lost.
return;
}

NSManagedObjectContext *objectContext = [[NSManagedObjectContext alloc] init];

// Ensure that strong pointers will be maintained between all fetched objects.
[objectContext setRetainsRegisteredObjects:YES];

// Setting the correct NSMergePolicy is important when modifying your model after initial creation.
[objectContext setMergePolicy:[[NSMergePolicy alloc] initWithMergeType:NSMergeByPropertyObjectTrumpMergePolicyType]];

[objectContext setPersistentStoreCoordinator:persistentStoreCoordinator];

 

Now you can use your NSManagedObjectContext and NSPersistentStoreCoordinator just as you normally would when you are using CoreData using the .xcdatamodeld file.

 

This is all I have time for at the moment. I will update this article very soon to make it more readable - but I think there is enough here to get you started. Check back soon!