Let start with the basics, what is core data? Core Data is a framework originally developed by Apple for MacOS but has become very popular in iOS app development. Core Data is used to manage the model layer objects in your applications, or simply put, it provides a powerful way of managing your applications persistent data using an Object Graph

If you don’t work with Core Data on a regular basis some of the terminology is very easy to forget. What I have done here is build a collection of the most frequently used classes and concepts in Core Data as an easy reference/cheat sheet.

NSPersistentContainer

The NSPersistentContainer handles the creation fo the Core Data Stack and offers access to the NSManagedObjectContext. (introduced for iOS at WWDC 2016)
Example Core Data stack creation

@interface MyDataController : NSObject

		

     @property (strong, nonatomic, readonly) NSPersistentContainer *persistentContainer;
	

     - (id)initWithCompletionBlock:(CallbackBlock)callback;
	

@end

		

@implementation MyDataController

		

     - (id)init

     {

          self = [super init];

          if (!self) return nil;

	  self.persistentContainer = [[NSPersistentContainer alloc] initWithName:@"DataModel"];

	  [self.persistentContainer loadPersistentStoresWithCompletionHandler:^(NSPersistentStoreDescription *description, NSError *error)               {

	            if (error != nil) {

	            NSLog(@"Failed to load Core Data stack: %@", error);

	            abort();

	       }

	       callback();

	  }];


          return self;

     }


@end

SWIFT

import UIKit
import CoreData

class DataController: NSObject {
     var managedObjectContext: NSManagedObjectContext
     init(completionClosure: @escaping () -> ()) {
          persistentContainer = NSPersistentContainer(name: "DataModel")
	  persistentContainer.loadPersistentStores() { (description, error) in
	       if let error = error {
	             fatalError("Failed to load Core Data stack: \(error)")
		}
		completionClosure()
          }
     }
}

NSPersistentStore

The NSPersistentStore represents your persistent storage which can be a Binary, XML, SQLite or InMemory (which is not persistent and great for testing) file.

In 2016 at WWDC Apple introduced the NSPersistentContainer which made working directly with the NSPersistentStore the rare exception. So unless you are the rare exception or need to support old versions of iOS, it is highly recommended to just use the NSPersistentContainer.

NSPersistentStoreCoordinator

The NSPersistentStoreCoordinator is the middleware in the Core Data Stack and is responsible for realizing/creating instances of entities that are defined inside of the model. It creates new instances of the entities in the model and it retrieves existing entities from a persistent store.

NSPersistentStoreDescription

The NSPersistentStoreDescription is new class Apple introduced along with NSPersistentContainer at WWDC 2016. This class is used to describe the configuration information to pass to the NSPersistentStoreCoordinator when adding a persistent store.

NSManagedObjectModel

An instance of the NSManagedObjectModel describes the data that is going to be accessed by the Core Data Stack.

NSManagedObjectContext

The NSManagedObjectContext is the scratch pad for all changes to your NSManagedObject subclass instances. It tracks individual object attributes/properties and relationships between objects to look for changes. All of these changes do not get stored in the persistent store until they are saved.

OBJECTIVE-C

-(NSManagedObjectContext *) managedContext {
     NSManagedObjectContext *context = nil;
     appDelegate = (AppDelegate *)[[UIApplication sharedApplication] delegate];
     context = appDelegate.coreDataStack.persistentContainer.viewContext;
     return context;
}

SWIFT

func managedContext() -> NSManagedObjectContext {
     var context:NSManagedObjectContext
     context = (appDelegate.coreDataStack.persistentContainer.viewContext)
     return context;
}

NSEntityDescription

To create new NSManagedObject subclass instances of your Core Data data, Apple provides the NSEntityDescription. The allows Core Data to create a new object instance of your data and place it in the NSManagedObjectContext instance. Remember new object instances will be stored in the persistent store until you save the changes in your context.

OBJECTIVE-C

     AAAEmployeeMO *employee = [NSEntityDescription insertNewObjectForEntityForName:@"Employee" inManagedObjectContext:[self managedObjectContext];

SWIFT

     let employee = NSEntityDescription.insertNewObjectForEntityForName("Employee", inManagedObjectContext: managedObjectContext) as! EmployeeMO

Entity + Attributes + Relationships

This brings use to one of the core concepts of Core Data. An entity can be thought of as a table in a database which represents the schema of the data stored there. These Entities that you create will have Attributes or columns in a database. If you think of Core Data as being a two sided solution. On one side you have the NSPersistentStore which has Entities and Attributes and the other side being your application which access these entities as NSManagedObject subclasses with your Attributes the properties of you NSManagedObject subclass.

Core Data supports to-one and to-many relationships, and fetched properties. Fetched properties represent weak, one-way relationships.

NSFetchRequest

To retrieve data from your Core Data stack requires using the NSFetchRequest. The fetch request must be passed to a NSManagedObjectContext which is responsible for managing these new objects for you.

OBJECTIVE-C

     NSManagedObjectContext *moc = [self managedObjectContext];
     NSFetchRequest *fetchRequest = [NSFetchRequest        fetchRequestWithEntityName:@"Employee"];


     NSError *error = nil;

     NSArray *results = [moc executeFetchRequest:request error:&error];

     if (!results) {

          NSLog(@"Error fetching Employee objects: %@\n%@", [error localizedDescription], [error userInfo]);

          abort();

      }

SWIFT

     let moc = self.managedObjectContext()
     let fetchRequest = NSFetchRequest(entityName: "Employee")
	
     do {
          let fetchedEmployees = try moc.executeFetchRequest(employeesFetch) as! [EmployeeMO]
     } catch {
          fatalError("Failed to fetch employees: \(error)")
     }

NSPredicate

To filter your NSFetchRequest you can use the same tools used for filtering collections and simply pass it to your NSFetchRequest predicate property.

OBJECTIVE-C

     NSString *firstName = @"Trevor";

     [fetchRequest setPredicate:[NSPredicate predicateWithFormat:@"firstName == %@", firstName]];

SWIFT

     let firstName = "Trevor"
     fetchRequest.predicate = NSPredicate(format: "firstName == %@", firstName)

NSSortDescriptor

To sort your NSFetchRequest you can use the same tools  you would use for sorting collections.

OBJECTIVE-C

     NSSortDescriptor *nameSort = [NSSortDescriptor sortDescriptorWithKey:@"name" ascending:YES];
     [request setSortDescriptors:@[nameSort]];

SWIFT

     let sortFavorite = NSSortDescriptor.init(key: "favorite", ascending: false)
     let sortName = NSSortDescriptor.init(key: "date", ascending: false)
     request.sortDescriptors = [sortFavorite, sortName]

NSFetchedResultsController

When working with UITableView’s and UICollectionView’s, Apple provides a handy controller that allows you to connect your Core Data with your Model’s and Storyboards. This is all accomplished using the NSFetchedResultsController and it’s delegate methods.

OBJECTIVE-C

@property (nonatomic, strong) NSFetchedResultsController *fetchedResultsController;

	

- (void)initializeFetchedResultsController

{

	NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:@"Person"];

	NSSortDescriptor *lastNameSort = [NSSortDescriptor sortDescriptorWithKey:@"lastName" ascending:YES];

	[request setSortDescriptors:@[lastNameSort]];

	NSManagedObjectContext *moc = …; //Retrieve the main queue NSManagedObjectContext

	[self setFetchedResultsController:[[NSFetchedResultsController alloc] initWithFetchRequest:request managedObjectContext:moc sectionNameKeyPath:nil cacheName:nil]];

	[[self fetchedResultsController] setDelegate:self];

	NSError *error = nil;

	if (![[self fetchedResultsController] performFetch:&error]) {

		NSLog(@"Failed to initialize FetchedResultsController: %@\n%@", [error localizedDescription], [error userInfo]);

		abort();

	}

}

SWIFT

var fetchedResultsController: NSFetchedResultsController!	

func initializeFetchedResultsController() {
	let request = NSFetchRequest(entityName: "Person")
	let departmentSort = NSSortDescriptor(key: "department.name", ascending: true)
	let lastNameSort = NSSortDescriptor(key: "lastName", ascending: true)
	request.sortDescriptors = [departmentSort, lastNameSort	
	let moc = dataController.managedObjectContext
	fetchedResultsController = NSFetchedResultsController(fetchRequest: request, managedObjectContext: moc, sectionNameKeyPath: nil, cacheName: nil)
	fetchedResultsController.delegate = self	
	do {
		try fetchedResultsController.performFetch()
	} catch {
		fatalError("Failed to initialize FetchedResultsController: \(error)")
	}
}

Once initialized you can now use the delegate methods to perform tasks on your UITableView or UICollectionView as changes occur in your context.

OBJECTIVE-C

#pragma mark - NSFetchedResultsControllerDelegate

- (void)controllerWillChangeContent:(NSFetchedResultsController *)controller

{

	[[self tableView] beginUpdates];

}

- (void)controller:(NSFetchedResultsController *)controller didChangeSection:(id )sectionInfo atIndex:(NSUInteger)sectionIndex forChangeType:(NSFetchedResultsChangeType)type

{

	switch(type) {

	     case NSFetchedResultsChangeInsert:

		[[self tableView] insertSections:[NSIndexSet indexSetWithIndex:sectionIndex] withRowAnimation:UITableViewRowAnimationFade];

	     break;

	     case NSFetchedResultsChangeDelete:

		[[self tableView] deleteSections:[NSIndexSet indexSetWithIndex:sectionIndex] withRowAnimation:UITableViewRowAnimationFade];

	     break;

	     case NSFetchedResultsChangeMove:

	     case NSFetchedResultsChangeUpdate:

	     break;

	}

}

	- (void)controller:(NSFetchedResultsController *)controller didChangeObject:(id)anObject atIndexPath:(NSIndexPath *)indexPath forChangeType:(NSFetchedResultsChangeType)type newIndexPath:(NSIndexPath *)newIndexPath

{

	switch(type) {

		case NSFetchedResultsChangeInsert:

		     [[self tableView] insertRowsAtIndexPaths:@[newIndexPath] withRowAnimation:UITableViewRowAnimationFade];

		break;

		case NSFetchedResultsChangeDelete:

		     [[self tableView] deleteRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationFade];

		break;

		case NSFetchedResultsChangeUpdate:

	     	     [self.tableView reloadRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationFade];

		break;

		case NSFetchedResultsChangeMove:

		     [[self tableView] deleteRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationFade];

		     [[self tableView] insertRowsAtIndexPaths:@[newIndexPath] withRowAnimation:UITableViewRowAnimationFade];

		break;

	}

}

- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller

{

	[[self tableView] endUpdates];

}

SWIFT

func controllerWillChangeContent(_ controller: NSFetchedResultsController) {
	tableView.beginUpdates()
}	
func controller(_ controller: NSFetchedResultsController, didChange sectionInfo: NSFetchedResultsSectionInfo, atSectionIndex sectionIndex: Int, for type: NSFetchedResultsChangeType) {
	switch type {
		case .insert:
	 	     tableView.insertSections(IndexSet(integer: sectionIndex), with: .fade)
		case .delete:
		     tableView.deleteSections(IndexSet(integer: sectionIndex), with: .fade)
		case .move:
		break
		case .update:
		break
	}
}
	
func controller(_ controller: NSFetchedResultsController, didChange anObject: Any, at indexPath: IndexPath?, for type: NSFetchedResultsChangeType, newIndexPath: IndexPath?) {
	switch type {
		case .insert:
	 	     tableView.insertRows(at: [newIndexPath!], with: .fade)
		case .delete:
		     tableView.deleteRows(at: [indexPath!], with: .fade)
		case .update:
		     tableView.reloadRows(at: [indexPath!], with: .fade)
		case .move:
		     tableView.moveRow(at: indexPath!, to: newIndexPath!)
	}
}
func controllerDidChangeContent(_ controller: NSFetchedResultsController) {
	tableView.endUpdates()
}

NSPersistentHistoryTransaction + NSPersistentHistoryChangeRequest

For iOS11, Apple brought a few new additional changes to fetch requesting in the form of NSPersistentHistoryTransaction and NSPersistentHistoryChangeRequest. Both these new classes now allow requests to pull data based on transaction history item or date. This allows for fetch requests of large or complex data to be more specific to the needs.

NSEntityMigrationPolicy

If your new model simply adds properties or entities to your existing model, there may be no need to write any custom code. If the transformation is more complex, however, you might need to create a subclass of NSEntityMigrationPolicy to perform the transformation; for example:

  • If you have a Person entity that also includes address information that you want to split into a separate Address entity, but you want to ensure uniqueness of each Address.
  • If you have an attribute that encodes data in a string format that you want to change to a binary representation.

The methods you override in a custom migration policy correspond to the different phases of the migration process—these are called out in the description of the process given in Three-Stage Migration.

Three-Stage Migration

The migration process itself is in three stages. It uses a copy of the source and destination models in which the validation rules are disabled and the class of all entities is changed to NSManagedObject.

To perform the migration, Core Data sets up two stacks, one for the source store and one for the destination store. Core Data then processes each entity mapping in the mapping model in turn. It fetches objects of the current entity into the source stack, creates the corresponding objects in the destination stack, then recreates relationships between destination objects in a second stage, before finally applying validation constraints in the final stage.

Before a cycle starts, the entity migration policy responsible for the current entity is sent a beginEntityMapping:manager:error: message. You can override this method to perform any initialization the policy requires. The process then proceeds as follows:

Create destination instances based on source instances.At the beginning of this phase, the entity migration policy is sent a createDestinationInstancesForSourceInstance:entityMapping:manager:error: message; at the end it is sent a endInstanceCreationForEntityMapping:manager:error: message.In this stage, only attributes (not relationships) are set in the destination objects.Instances of the source entity are fetched. For each instance, appropriate instances of the destination entity are created (typically there is only one) and their attributes populated (for trivial cases, name = $source.name). A record is kept of the instances per entity mapping since this may be useful in the second stage.
Recreate relationships.At the beginning of this phase, the entity migration policy is sent a createRelationshipsForDestinationInstance:entityMapping:manager:error: message; at the end it is sent a endRelationshipCreationForEntityMapping:manager:error: message.For each entity mapping (in order), for each destination instance created in the first step any relationships are recreated.

Validate and save.In this phase, the entity migration policy is sent a performCustomValidationForEntityMapping:manager:error: message.Validation rules in the destination model are applied to ensure data integrity and consistency, and then the store is saved.

At the end of the cycle, the entity migration policy is sent an endEntityMapping:manager:error: message. You can override this method to perform any clean-up the policy needs to do.

1 Comment

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google+ photo

You are commenting using your Google+ account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s