public
Last active

Core Data singleton manager class capable of being run from a static library

  • Download Gist
DataManager.h
Objective-C
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
// 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
Objective-C
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153
// 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

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.

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

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.

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

What is the best way to implement undoManager?

@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.)

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

@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.

Please sign in to comment on this gist.

Something went wrong with that request. Please try again.