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 managedObjectContextmanagedObjectModel, 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!

Enjoy the article? Join over 14,500+ Swift developers and enthusiasts who get my weekly updates.

  • I think Simple implementation tutorial how using this CoreData Singleton would be great !

  • alexchoi

    I’m curious as to why you didn’t set up anything to modify entities

    • I just didn’t need it for my app, ShopLater, since the items were parsed and saved from an outside source (the shopping website) and there was no way for the user to modify the items.

  • susemi99

    thank you very much

  • Mike R

    excellent code, just a little advice

    //the database must be in the cache folder never in documents because this will make an app store rejection for using documents folder that is automatically backed by the apple cloud, so it must be in a disposable folder.
    NSURL *documentDirectoryURL = [fileManager URLsForDirectory:NSCachesDirectory inDomains:NSUserDomainMask][0];

  • George Taylor

    Mike R – If you create a new Project within XCode 5 and include CoreData it adds boiler plate code for CoreData in Appdelegate.m
    Now I maybe wrong but this looks like the Documents directory to me. So why would Apple automatically add code that gets you an App Store fail?

    #pragma mark – Application’s Documents directory

    // Returns the URL to the application’s Documents directory.
    – (NSURL *)applicationDocumentsDirectory
    {
    return [[[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] lastObject];
    }

  • Mike Strand

    Nice post. Thx.