iOS: My Core Data Singleton Example
The first iOS app I built, ShopLater (it hasn’t made it to the app store yet unfortunately), used a lot of Core Data. The app makes it easy to shop and save the items you want to buy later. The saving part happens all through Core Data.
Since you only want to initialize all the core data stuff once, it’s a great candidate for the Singleton Pattern. To create a Singleton in Objective-C, you can use the Grand Central Dispatch handy dispatch_once method.
While I started out by creating a create and fetch method for each one of my entities – Product, Price, Image, Provider, I soon saw a pattern. Using the popular DRY (don’t repeat yourself) mantra from Rails development, I thought about how Active Record works, and came up with more generic ways to create and retrieve entities.
This is my first Core Data singleton, and I only built it out as I needed for the purpose of my app, however, I plan on adding to it and refining it as I keep building more apps. Enjoy!
CoreDataManager.h
The CoreDataManager.h file exposes all the CoreDataManager methods to other parts of the application. While I’d like to build up this list of CoreData methods in the future, it currently only has the methods I needed for the purpose of my application. You can get the CoreDataManager instance, create, fetch, save, and delete Entities, and check uniqueness of an attribute:
// // CoreDataManager.h // ShopLater // // Created by Natasha Murashev on 6/7/13. // Copyright (c) 2013 Natasha Murashev. All rights reserved. // #import <Foundation/Foundation.h> #import <CoreData/CoreData.h> @interface CoreDataManager : NSObject + (CoreDataManager *)sharedManager; - (void)saveDataInManagedContextUsingBlock:(void (^)(BOOL saved, NSError *error))savedBlock; - (NSFetchedResultsController *)fetchEntitiesWithClassName:(NSString *)className sortDescriptors:(NSArray *)sortDescriptors sectionNameKeyPath:(NSString *)sectionNameKeypath predicate:(NSPredicate *)predicate; - (id)createEntityWithClassName:(NSString *)className attributesDictionary:(NSDictionary *)attributesDictionary; - (void)deleteEntity:(NSManagedObject *)entity; - (BOOL)uniqueAttributeForClassName:(NSString *)className attributeName:(NSString *)attributeName attributeValue:(id)attributeValue; @end
Constants.h
Since I plan on using this CoreData Singleton across many projects, I like keeping it as generic as possible, which means extracting out the name of the momd file, which I name after my project name. I keep the name of the project in my Constants.h file, and I’ll use the sProjectName constant when I initially create the CoreDataManager singleton.
// // Constants.h // ShopLater // // Created by Natasha Murashev on 6/10/13. // Copyright (c) 2013 Natasha Murashev. All rights reserved. // static NSString *sProjectName = @"ShopLater";
CoreDataManager.m
This is the actual implementation of the CoreDataManager singleton. I like to keep the managedObjectContext, managedObjectModel, and the persistentStoreCoordinator all private – only the CoreDataManager should be responsible for figuring out how to interact with these.
// // CoreDataManager.m // ShopLater // // Created by Natasha Murashev on 6/7/13. // Copyright (c) 2013 Natasha Murashev. All rights reserved. // #import "CoreDataManager.h" #import "Constants.h" @interface CoreDataManager () @property (strong, nonatomic) NSManagedObjectContext *managedObjectContext; @property (strong, nonatomic) NSManagedObjectModel *managedObjectModel; @property (strong, nonatomic) NSPersistentStoreCoordinator *persistentStoreCoordinator; - (void)setupManagedObjectContext; @end @implementation CoreDataManager static CoreDataManager *coreDataManager; + (CoreDataManager *)sharedManager { if (!coreDataManager) { static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ coreDataManager = [[CoreDataManager alloc] init]; }); } return coreDataManager; } #pragma mark - setup - (id)init { self = [super init]; if (self) { [self setupManagedObjectContext]; } return self; } - (void)setupManagedObjectContext { NSFileManager *fileManager = [NSFileManager defaultManager]; NSURL *documentDirectoryURL = [fileManager URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask][0]; NSURL *persistentURL = [documentDirectoryURL URLByAppendingPathComponent:[NSString stringWithFormat:@"%@.sqlite", sProjectName]]; NSURL *modelURL = [[NSBundle mainBundle] URLForResource:sProjectName withExtension:@"momd"]; self.managedObjectModel = [[NSManagedObjectModel alloc] initWithContentsOfURL:modelURL]; self.persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:self.managedObjectModel]; NSError *error = nil; NSPersistentStore *persistentStore = [self.persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:persistentURL options:nil error:&error]; if (persistentStore) { self.managedObjectContext = [[NSManagedObjectContext alloc] init]; self.managedObjectContext.persistentStoreCoordinator = self.persistentStoreCoordinator; } else { NSLog(@"ERROR: %@", error.description); } } - (void)saveDataInManagedContextUsingBlock:(void (^)(BOOL saved, NSError *error))savedBlock { NSError *saveError = nil; savedBlock([self.managedObjectContext save:&saveError], saveError); } - (NSFetchedResultsController *)fetchEntitiesWithClassName:(NSString *)className sortDescriptors:(NSArray *)sortDescriptors sectionNameKeyPath:(NSString *)sectionNameKeypath predicate:(NSPredicate *)predicate { NSFetchedResultsController *fetchedResultsController; NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init]; NSEntityDescription *entity = [NSEntityDescription entityForName:className inManagedObjectContext:self.managedObjectContext]; fetchRequest.entity = entity; fetchRequest.sortDescriptors = sortDescriptors; fetchRequest.predicate = predicate; fetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:self.managedObjectContext sectionNameKeyPath:sectionNameKeypath cacheName:nil]; NSError *error = nil; BOOL success = [fetchedResultsController performFetch:&error]; if (!success) { NSLog(@"fetchManagedObjectsWithClassName ERROR: %@", error.description); } return fetchedResultsController; } - (id)createEntityWithClassName:(NSString *)className attributesDictionary:(NSDictionary *)attributesDictionary { NSManagedObject *entity = [NSEntityDescription insertNewObjectForEntityForName:className inManagedObjectContext:self.managedObjectContext]; [attributesDictionary enumerateKeysAndObjectsUsingBlock:^(NSString *key, id obj, BOOL *stop) { [entity setValue:obj forKey:key]; }]; return entity; } - (void)deleteEntity:(NSManagedObject *)entity { [self.managedObjectContext deleteObject:entity]; } - (BOOL)uniqueAttributeForClassName:(NSString *)className attributeName:(NSString *)attributeName attributeValue:(id)attributeValue { NSPredicate *predicate = [NSPredicate predicateWithFormat:@"%K like %@", attributeName, attributeValue]; NSArray *sortDescriptors = @[[NSSortDescriptor sortDescriptorWithKey:attributeName ascending:YES]]; NSFetchedResultsController *fetchedResultsController = [self fetchEntitiesWithClassName:className sortDescriptors:sortDescriptors sectionNameKeyPath:nil predicate:predicate]; return fetchedResultsController.fetchedObjects.count == 0; } @end
If you have your own CoreData Singleton or have any suggestions on how to make mine better, please leave a comment below!