Create a gist now

Instantly share code, notes, and snippets.

What would you like to do?
An Objective-C base class for a basic CoreData data model implemented in SQLite, compiling all of the boilerplate code for setting up the necessary trio of CoreData classes (NSManagedObjectContext, NSManagedObjectModel, and NSPersistentStoreCoordinator) and providing a simplified interface for creating, fetching, and deleting NSManagedObject sub…
//
// SimpleDatabase.h
//
// Copyright (c) 2013 Erik Ralston
//
// MIT License
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
#import <Foundation/Foundation.h>
#import <CoreData/CoreData.h>
///
/// Base class for CoreData SQLite, child classes should implement application-specific logic on top if its simplified interface
/// NOTE: This database should only be accessed from the main thread of the application due to it using a singleton NSManagedObjectContext
///
@interface SimpleDatabase : NSObject
// Properties
///
/// Name of the object model related to this database (Same as the DataModel)
/// Child classes should set this property to indicate the object model
/// EG: AGDataModel, Library
///
@property (nonatomic) NSString *objectModelName;
///
/// The name of the SQLite file
/// Child classes should set this property to indicate the desired filename
/// EG: AGDataModel.sqlite, library_data.sqlite
///
@property (nonatomic) NSString *fileName;
// DB Methods
- (void)save;
- (void)update;
- (void)deleteEntireDatabase;
// Create Methods
- (id)create:(NSString *)entityName;
// Fetch Methods
-(NSFetchRequest *)createFetchForEntity:(NSString *)entityName;
-(NSFetchRequest *)createFetchForEntity:(NSString *)entityName SortedBy:(NSString *)sortKey ascending:(BOOL)isAscending;
- (NSArray *)fetch:(NSFetchRequest *)request;
- (NSArray *)fetchAll:(NSString *)entityName;
- (id)fetchOne:(NSFetchRequest *)request;
- (NSArray *)fetch:(NSString *)entityName withPredicate:(NSPredicate *)predicate;
- (id)fetchOne:(NSString *)entityName withPredicate:(NSPredicate *)predicate;
- (NSFetchedResultsController *)createFetchController:(NSFetchRequest *)fetchRequest;
- (NSFetchedResultsController *)createFetchControllerForEntity:(NSString *)entityName sortedBy:(NSString *)sortKey ascending:(BOOL)isAscending;
// Delete Methods
- (void)deleteOne:(NSManagedObject *)object;
- (void)deleteEach:(id<NSFastEnumeration>)objects;
- (void)deleteAll:(NSString *)entityName;
@end
//
// SimpleDatabase.m
//
// Copyright (c) 2013 Erik Ralston
//
// MIT License
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
#import "SimpleDatabase.h"
@interface SimpleDatabase()
{
NSManagedObjectContext *_context;
NSManagedObjectModel *_objectModel;
NSPersistentStoreCoordinator *_coordinator;
}
@property (nonatomic, readonly) NSManagedObjectContext *context;
@property (nonatomic, readonly) NSManagedObjectModel *objectModel;
@property (nonatomic, readonly) NSPersistentStoreCoordinator *coordinator;
@end
@implementation SimpleDatabase
#pragma mark - Properties
///
/// Returns the NSURL for the application's documents directory
///
+ (NSURL *)applicationDocumentsDirectory {
return [[[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] lastObject];
}
///
/// The Managed Object Context for this database, automatically setup on first call
/// NOTE: This single context is used for all calls in this system, thus it is only safe to use DB instance from a single thread
///
- (NSManagedObjectContext *)context
{
if(!_context) {
NSPersistentStoreCoordinator *coordinator = [self coordinator];
if (coordinator != nil) {
_context = [[NSManagedObjectContext alloc] init];
[_context setPersistentStoreCoordinator:coordinator];
}
}
return _context;
}
///
/// The Managed Object Model for this database, automatically setup on first call
/// NOTE: The value for self.objectModelName must the named of a valid data model (.xcdatamodeld) defined in the project
///
- (NSManagedObjectModel *)objectModel
{
if(!_objectModel) {
// WARNING: self.objectModelName must have a value; otherwise, this might blow up!
NSURL *modelURL = [[NSBundle mainBundle] URLForResource:self.objectModelName withExtension:@"momd"];
_objectModel = [[NSManagedObjectModel alloc] initWithContentsOfURL:modelURL];
}
return _objectModel;
}
///
/// The Persistent Store Coordinator for this database, automatically setup on first call
/// NOTE: The value for self.fileName must be defined (EG: database.sqlite) or this will crash
///
- (NSPersistentStoreCoordinator *)coordinator
{
if(!_coordinator) {
// WARNING: self.fileName must have a value; otherwise, this might blow up!
NSURL *storeURL = [[SimpleDatabase applicationDocumentsDirectory] URLByAppendingPathComponent:self.fileName];
NSError *error = nil;
NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithBool:YES], NSMigratePersistentStoresAutomaticallyOption,
[NSNumber numberWithBool:YES], NSInferMappingModelAutomaticallyOption, nil];
_coordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:[self objectModel]];
if (![_coordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:options error:&error]) {
/*
Replace this implementation with code to handle the error appropriately.
abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
Typical reasons for an error here include:
* The persistent store is not accessible;
* The schema for the persistent store is incompatible with current managed object model.
Check the error message to determine what the actual problem was.
If the persistent store is not accessible, there is typically something wrong with the file path. Often, a file URL is pointing into the application's resources directory instead of a writeable directory.
If you encounter schema incompatibility errors during development, you can reduce their frequency by:
* Simply deleting the existing store:
[[NSFileManager defaultManager] removeItemAtURL:storeURL error:nil]
* Performing automatic lightweight migration by passing the following dictionary as the options parameter:
@{NSMigratePersistentStoresAutomaticallyOption:@YES, NSInferMappingModelAutomaticallyOption:@YES}
Lightweight migration will only work for a limited set of schema changes; consult "Core Data Model Versioning and Data Migration Programming Guide" for details.
*/
[[NSFileManager defaultManager] removeItemAtURL:storeURL error:nil];
NSLog(@"SimpleDatabase: Unresolved Persistent Store error %@, %@", error, [error userInfo]);
abort();
}
NSDictionary *fileAttributes = [NSDictionary dictionaryWithObject:NSFileProtectionComplete forKey:NSFileProtectionKey];
if (![[NSFileManager defaultManager] setAttributes:fileAttributes ofItemAtPath:[storeURL path] error:&error])
{
NSLog(@"SimpleDatabase: Unresolved FileProtectionComplete error %@, %@", error, [error userInfo]);
abort();
}
}
return _coordinator;
}
#pragma mark - Database Methods
///
/// Saves the current object context, flushing to file
/// NOTE: This must be called after all object creation, update, and deletion to persist changes
///
- (void)save
{
NSError *error = nil;
NSManagedObjectContext *managedObjectContext = [self context];
if (managedObjectContext != nil) {
if ([managedObjectContext hasChanges] && ![managedObjectContext save:&error]) {
// Replace this implementation with code to handle the error appropriately.
// abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development. If it is not possible to recover from the error, display an alert panel that instructs the user to quit the application by pressing the Home button.
NSLog(@"SimpleDatabase: Unresolved error %@, %@", error, [error userInfo]);
abort();
}
}
}
///
/// Processes pending changes to the object graph without doing a save
///
-(void)update
{
[[self context] processPendingChanges];
}
///
/// Destroys the entire db file and re-initialize it
///
- (void)deleteEntireDatabase
{
NSManagedObjectContext *context = [self context];
NSPersistentStoreCoordinator *coordinator = [self coordinator];
NSURL * storeURL = [coordinator URLForPersistentStore:[[coordinator persistentStores] lastObject]];
[self.context lock];
// Notify those that care to release their reference of the MOC
[[NSNotificationCenter defaultCenter] postNotificationName:@"MocUpdate" object:@"PreUpdate"];
[context reset];
if ([coordinator removePersistentStore:coordinator.persistentStores.lastObject error:nil])
{
// remove the file containing the data
[[NSFileManager defaultManager] removeItemAtURL:storeURL error:nil];
// Remove the reference to the persistent store coordinator and have it re-created
NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithBool:YES], NSMigratePersistentStoresAutomaticallyOption,
[NSNumber numberWithBool:YES], NSInferMappingModelAutomaticallyOption, nil];
[coordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:options error:nil];//creates a new persistent store
}
[context unlock];
[[NSNotificationCenter defaultCenter] postNotificationName:@"MocUpdate" object:@"PostUpdate"];
NSLog(@"SimpleDatabase: Reset local database");
}
#pragma mark - Create Methods
///
/// Creates a new object instance of the given entity name
/// NOTE: You must call the "save" method to persist this change to file
///
- (id)create:(NSString *)entityName
{
NSLog(@"SimpleDatabase: Creating new object for entity type %@", entityName);
return [NSEntityDescription insertNewObjectForEntityForName:entityName inManagedObjectContext:[self context]];
}
#pragma mark - Fetch Methods
///
/// Creates a fetch request for the given entity
///
-(NSFetchRequest *)createFetchForEntity:(NSString *)entityName
{
return [[NSFetchRequest alloc] initWithEntityName:entityName];
}
///
/// Executes the given fetch request, returning the result as an array
///
- (NSArray *)fetch:(NSFetchRequest *)request
{
NSLog(@"SimpleDatabase: Executing fetch request for entity '%@' with query: '%@'", [request entityName], [[request predicate] predicateFormat]);
NSError *error = nil;
NSArray *result = [[self context] executeFetchRequest:request error:&error];
if(error) {
NSLog(@"SimpleDatabase: Error executing fetch: %@", [error localizedDescription]);
}
return result;
}
///
/// Retrieves all objects with the given entity name
///
- (NSArray *)fetchAll:(NSString *)entityName
{
NSFetchRequest *request = [self createFetchForEntity:entityName];
return [self fetch:request];
}
///
/// Fetches a single object for the given request (the last one)
///
- (id)fetchOne:(NSFetchRequest *)request
{
NSArray *result = [self fetch:request];
return [result firstObject];
}
///
/// Executes a request for the given entity with the given predicate format, returning the resulting array
/// This is intended to return from a query with multiple values
///
- (NSArray *)fetch:(NSString *)entityName withPredicate:(NSPredicate *)predicate
{
NSFetchRequest* request = [self createFetchForEntity:entityName];
[request setPredicate:predicate];
return [self fetch:request];
}
///
/// Executes a request for the given entity with the given predicate format, returning the resulting array
/// This is intended to return from a query with multiple values
///
- (id)fetchOne:(NSString *)entityName withPredicate:(NSPredicate *)predicate
{
NSFetchRequest* request = [self createFetchForEntity:entityName];
[request setPredicate:predicate];
return [self fetchOne:request];
}
///
/// Creates a fetch for the given entity with the given sort mode
///
-(NSFetchRequest *)createFetchForEntity:(NSString *)entityName SortedBy:(NSString *)sortKey ascending:(BOOL)isAscending
{
NSFetchRequest *fetchRequest = [self createFetchForEntity:entityName];
NSLog(@"SimpleDatabase: Creating NSFetchRequest for entity %@ with predicate '%@' sorting by '%@' %@", [fetchRequest entityName], [[fetchRequest predicate] description], sortKey, isAscending ? @"ASC" : @"DESC");
// Configure the request's entity, and optionally its predicate.
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:sortKey ascending:isAscending];
NSArray *sortDescriptors = [[NSArray alloc] initWithObjects:sortDescriptor, nil];
[fetchRequest setSortDescriptors:sortDescriptors];
return fetchRequest;
}
///
/// Creates a fetch controller from the given request
///
- (NSFetchedResultsController *)createFetchController:(NSFetchRequest *)fetchRequest
{
return [[NSFetchedResultsController alloc]
initWithFetchRequest:fetchRequest
managedObjectContext:self.context
sectionNameKeyPath:nil
cacheName:nil];
}
///
/// Creates a NSFetchedResultsController object for the given entity name and sorting
///
-(NSFetchedResultsController *)createFetchControllerForEntity:(NSString *)entityName sortedBy:(NSString *)sortKey ascending:(BOOL)isAscending
{
NSFetchRequest *fetchRequest = [self createFetchForEntity:entityName SortedBy:sortKey ascending:isAscending];
return [self createFetchController:fetchRequest];
}
#pragma mark - Delete Methods
///
/// Deletes the given object from the object store
/// NOTE: You must call the "save" method to persist this change to file
///
- (void)deleteOne:(NSManagedObject *)object
{
NSLog(@"SimpleDatabase: Deleting object %@", object);
[[self context] deleteObject:object];
}
///
/// Deletes all of the objects in the given collection from the database
/// NOTE: You must call the "save" method to persist this change to file
///
-(void)deleteEach:(id<NSFastEnumeration>)objects
{
// Iterate through the collection, deleting each one
for (id obj in objects) {
[self deleteOne:obj];
}
}
///
/// Deletes all of the objects for the given entity
/// NOTE: You must call the "save" method to persist this change to file
///
-(void)deleteAll:(NSString *)entityName
{
NSArray *instances = [self fetchAll:entityName];
[self deleteEach:instances];
}
@end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment