Skip to content

Instantly share code, notes, and snippets.

@NachoMan
Created April 15, 2011 21:15
Show Gist options
  • Star 30 You must be signed in to star a gist
  • Fork 9 You must be signed in to fork a gist
  • Save NachoMan/922496 to your computer and use it in GitHub Desktop.
Save NachoMan/922496 to your computer and use it in GitHub Desktop.
Core Data singleton manager class capable of being run from a static library
// DataManager.h
#import <Foundation/Foundation.h>
#import <CoreData/CoreData.h>
extern NSString * const DataManagerDidSaveNotification;
extern NSString * const DataManagerDidSaveFailedNotification;
@interface DataManager : NSObject {
}
@property (nonatomic, readonly, retain) NSManagedObjectModel *objectModel;
@property (nonatomic, readonly, retain) NSManagedObjectContext *mainObjectContext;
@property (nonatomic, readonly, retain) NSPersistentStoreCoordinator *persistentStoreCoordinator;
+ (DataManager*)sharedInstance;
- (BOOL)save;
- (NSManagedObjectContext*)managedObjectContext;
@end
// DataManager.m
#import "DataManager.h"
NSString * const DataManagerDidSaveNotification = @"DataManagerDidSaveNotification";
NSString * const DataManagerDidSaveFailedNotification = @"DataManagerDidSaveFailedNotification";
@interface DataManager ()
- (NSString*)sharedDocumentsPath;
@end
@implementation DataManager
@synthesize persistentStoreCoordinator = _persistentStoreCoordinator;
@synthesize mainObjectContext = _mainObjectContext;
@synthesize objectModel = _objectModel;
NSString * const kDataManagerBundleName = @"MyApp";
NSString * const kDataManagerModelName = @"MyApp";
NSString * const kDataManagerSQLiteName = @"MyApp.sqlite";
+ (DataManager*)sharedInstance {
static dispatch_once_t pred;
static DataManager *sharedInstance = nil;
dispatch_once(&pred, ^{ sharedInstance = [[self alloc] init]; });
return sharedInstance;
}
- (void)dealloc {
[self save];
[_persistentStoreCoordinator release];
[_mainObjectContext release];
[_objectModel release];
[super dealloc];
}
- (NSManagedObjectModel*)objectModel {
if (_objectModel)
return _objectModel;
NSBundle *bundle = [NSBundle mainBundle];
if (kDataManagerBundleName) {
NSString *bundlePath = [[NSBundle mainBundle] pathForResource:kDataManagerBundleName ofType:@"bundle"];
bundle = [NSBundle bundleWithPath:bundlePath];
}
NSString *modelPath = [bundle pathForResource:kDataManagerModelName ofType:@"momd"];
_objectModel = [[NSManagedObjectModel alloc] initWithContentsOfURL:[NSURL fileURLWithPath:modelPath]];
return _objectModel;
}
- (NSPersistentStoreCoordinator*)persistentStoreCoordinator {
if (_persistentStoreCoordinator)
return _persistentStoreCoordinator;
// Get the paths to the SQLite file
NSString *storePath = [[self sharedDocumentsPath] stringByAppendingPathComponent:kDataManagerSQLiteName];
NSURL *storeURL = [NSURL fileURLWithPath:storePath];
// Define the Core Data version migration options
NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithBool:YES], NSMigratePersistentStoresAutomaticallyOption,
[NSNumber numberWithBool:YES], NSInferMappingModelAutomaticallyOption,
nil];
// Attempt to load the persistent store
NSError *error = nil;
_persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:self.objectModel];
if (![_persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType
configuration:nil
URL:storeURL
options:options
error:&error]) {
NSLog(@"Fatal error while creating persistent store: %@", error);
abort();
}
return _persistentStoreCoordinator;
}
- (NSManagedObjectContext*)mainObjectContext {
if (_mainObjectContext)
return _mainObjectContext;
// Create the main context only on the main thread
if (![NSThread isMainThread]) {
[self performSelectorOnMainThread:@selector(mainObjectContext)
withObject:nil
waitUntilDone:YES];
return _mainObjectContext;
}
_mainObjectContext = [[NSManagedObjectContext alloc] init];
[_mainObjectContext setPersistentStoreCoordinator:self.persistentStoreCoordinator];
return _mainObjectContext;
}
- (BOOL)save {
if (![self.mainObjectContext hasChanges])
return YES;
NSError *error = nil;
if (![self.mainObjectContext save:&error]) {
NSLog(@"Error while saving: %@\n%@", [error localizedDescription], [error userInfo]);
[[NSNotificationCenter defaultCenter] postNotificationName:DataManagerDidSaveFailedNotification
object:error];
return NO;
}
[[NSNotificationCenter defaultCenter] postNotificationName:DataManagerDidSaveNotification object:nil];
return YES;
}
- (NSString*)sharedDocumentsPath {
static NSString *SharedDocumentsPath = nil;
if (SharedDocumentsPath)
return SharedDocumentsPath;
// Compose a path to the <Library>/Database directory
NSString *libraryPath = [[NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, YES) objectAtIndex:0] retain];
SharedDocumentsPath = [[libraryPath stringByAppendingPathComponent:@"Database"] retain];
// Ensure the database directory exists
NSFileManager *manager = [NSFileManager defaultManager];
BOOL isDirectory;
if (![manager fileExistsAtPath:SharedDocumentsPath isDirectory:&isDirectory] || !isDirectory) {
NSError *error = nil;
NSDictionary *attr = [NSDictionary dictionaryWithObject:NSFileProtectionComplete
forKey:NSFileProtectionKey];
[manager createDirectoryAtPath:SharedDocumentsPath
withIntermediateDirectories:YES
attributes:attr
error:&error];
if (error)
NSLog(@"Error creating directory path: %@", [error localizedDescription]);
}
return SharedDocumentsPath;
}
- (NSManagedObjectContext*)managedObjectContext {
NSManagedObjectContext *ctx = [[[NSManagedObjectContext alloc] init] autorelease];
[ctx setPersistentStoreCoordinator:self.persistentStoreCoordinator];
return ctx;
}
@end
@odowd
Copy link

odowd commented Mar 16, 2012

Are there any example projects using DataManager? I am having a hard time understanding what is left in the XCode Core Data AppDelegate after pulling this functionality across to DataManager.

@NachoMan
Copy link
Author

Yeah, though I can't say which ones. The problem is that your AppDelegate should only contain the most basic of things: What to do when your app starts up, what to do when your app goes into the background, when it becomes active, etc. Cluttering it up with Core Data house-cleaning overloads what the AppDelegate is meant to do. For example, in your AppDelegate's willEnterBackground, you can make a simple one-line call to [[DataManager sharedInstance] save];

See my blog post on the topic here: http://nachbaur.com/blog/smarter-core-data

@odowd
Copy link

odowd commented Mar 18, 2012

Thanks - I have read the blog post with great interest. XCore 4.2.1 seems to have made some of the changes to the template already - for example it versions properly through the call to initWithContentsOfURL. I'd now like to set up an XCode template to do it your way.

I note DataManager needs to have the name of its bundle hardcoded (@"MyApp"). Is there any way it can detect the name of the bundle it is included in? That would avoid having to make local copies of the files for every project.

@rojotek
Copy link

rojotek commented Apr 11, 2012

Hey - just did a fork to remove the "retain" and "dealloc" methods which aren't ARC friendly.
https://gist.github.com/2362546

@jostster
Copy link

What is the best way to implement undoManager?

@phillbaker
Copy link

@NachoMan, thanks for posting this and @rojotek for the update.

I'd like to echo @odowd's last question, what's the reasoning behind hardcoding the app bundle name? Seems like the mainBundle would be adequate. (I have to comment out L#46-50 in the .m file, or it doesn't find the url in the bundle and throws an exception, it's not finding my bundle via the name.)

@NachoMan
Copy link
Author

NachoMan commented Jul 2, 2012

No reason really, you can just as easily make it programmatic by introspecting the NSBundle object and fetching the target name.

@lukaspechar
Copy link

@NachoMan thanks for this great helper class. Got everything to work, though as @pillbaker said I used the [NSBundle mainBundle] to get the momd. I have a question regarding
-(NSManagedObjectContext*)managedObjectContext method. What is it's use? Initially I though that is the NSManagedObjectContext I should have used but later noticed it is the mainObjectContext I should be using.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment