Skip to content

Instantly share code, notes, and snippets.

@kwent
Created September 17, 2014 07:47
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save kwent/51ca208c9cfd4cc47817 to your computer and use it in GitHub Desktop.
Save kwent/51ca208c9cfd4cc47817 to your computer and use it in GitHub Desktop.
STDataStoreController
//
// STDataStoreController.h
//
// Created by Buzz Andersen on 3/24/11.
// Copyright 2011 System of Touch. All rights reserved.
//
#import <Foundation/Foundation.h>
#import <CoreData/CoreData.h>
@class STTargetActionQueue;
extern NSString *STDataStoreControllerWillClearDatabaseNotification;
extern NSString *STDataStoreControllerDidClearDatabaseNotification;
@interface STDataStoreController : NSObject {
NSString *identifier;
NSString *managedObjectModelPath;
NSString *rootDirectory;
NSManagedObjectModel *managedObjectModel;
NSPersistentStoreCoordinator *persistentStoreCoordinator;
NSMergePolicy *mergePolicy;
dispatch_queue_t workerQueue;
STTargetActionQueue *observerInfo;
}
@property (nonatomic, retain) NSString *identifier;
@property (nonatomic, retain) NSString *managedObjectModelPath;
@property (nonatomic, retain) NSString *rootDirectory;
@property (nonatomic, retain, readonly) NSString *persistentStorePath;
@property (nonatomic, retain, readonly) NSManagedObjectContext *mainContext;
@property (nonatomic, assign) NSMergePolicy *mergePolicy;
@property (nonatomic, assign, readonly) dispatch_queue_t workerQueue;
// Class Methods
+ (NSString *)defaultRootDirectory;
// Initialization
- (id)initWithIdentifier:(NSString *)inIdentifier;
- (id)initWithIdentifier:(NSString *)inIdentifier rootDirectory:(NSString *)inRootDirectory;
- (id)initWithIdentifier:(NSString *)inIdentifier rootDirectory:(NSString *)inRootDirectory modelPath:(NSString *)inModelPath;
// Managed Object Contexts
- (NSManagedObjectContext *)threadContext;
// Reset/Clear Database
- (void)save;
- (void)reset;
- (void)deletePersistentStore;
// Methods for Subclassing
- (NSString *)persistentStorePathForIdentifier:(NSString *)inIdentifier;
- (void)processChangesMergedToMainContext;
// Change Observation
- (void)addObserver:(id)inObserver action:(SEL)inAction forEntityName:(NSString *)inEntityName;
- (void)removeObserver:(id)inObserver forEntityName:(NSString *)inEntityName;
- (void)removeObserver:(id)inObserver;
@end
//
// STDataStoreController.m
//
// Created by Buzz Andersen on 3/24/11.
// Copyright 2011 System of Touch. All rights reserved.
//
#import "STDataStoreController.h"
#import "STUtils.h"
// Notifications
NSString *STDataStoreControllerWillClearDatabaseNotification = @"STDataStoreControllerWillClearDatabaseNotification";
NSString *STDataStoreControllerDidClearDatabaseNotification = @"STDataStoreControllerDidClearDatabaseNotification";
NSString *STDataStoreControllerThreadContextKey = @"STDataStoreControllerThreadContextKey";
@interface STDataStoreController ()
@property (nonatomic, retain) NSManagedObjectModel *managedObjectModel;
@property (nonatomic, retain) NSString *persistentStorePath;
@property (nonatomic, retain) NSPersistentStoreCoordinator *persistentStoreCoordinator;
@property (nonatomic, retain) STTargetActionQueue *observerInfo;
- (NSManagedObjectContext *)_contextForThread:(NSThread *)inThread;
- (NSString *)_currentThreadContextKey;
- (NSPersistentStoreCoordinator *)_persistentStoreCoordinatorForPath:(NSString *)inPath;
- (void)_updatePersistentStorePath;
@end
@implementation STDataStoreController
@synthesize identifier;
@synthesize managedObjectModelPath;
@synthesize rootDirectory;
@synthesize persistentStorePath;
@synthesize managedObjectModel;
@synthesize persistentStoreCoordinator;
@synthesize mergePolicy;
@synthesize observerInfo;
#pragma mark Class
+ (NSString *)defaultRootDirectory;
{
NSMutableArray *pathComponents = [[NSMutableArray alloc] init];
// If we're on a Mac, include the app name in the
// application support path.
#if !TARGET_OS_IPHONE
[pathComponents addObject:[[NSFileManager defaultManager] applicationSupportPathIncludingAppName]];
#else
[pathComponents addObject:[[NSFileManager defaultManager] applicationSupportPath]];
#endif
[pathComponents addObject:NSStringFromClass([self class])];
NSString *path = [NSString pathWithComponents:pathComponents];
[pathComponents release];
return path;
}
#pragma mark Initialization
- (id)initWithIdentifier:(NSString *)inIdentifier;
{
if (!(self = [self initWithIdentifier:inIdentifier rootDirectory:nil])) {
return nil;
}
return self;
}
- (id)initWithIdentifier:(NSString *)inIdentifier rootDirectory:(NSString *)inRootDirectory;
{
if (!(self = [self initWithIdentifier:inIdentifier rootDirectory:inRootDirectory modelPath:nil])) {
return nil;
}
return self;
}
- (id)initWithIdentifier:(NSString *)inIdentifier rootDirectory:(NSString *)inRootDirectory modelPath:(NSString *)inModelPath;
{
if (!(self = [super init])) {
return nil;
}
identifier = [inIdentifier retain];
rootDirectory = inRootDirectory.length ? [inRootDirectory retain] : [[[self class] defaultRootDirectory] retain];
[self _updatePersistentStorePath];
managedObjectModelPath = [inModelPath retain];
mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy;
workerQueue = NULL;
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(_mergeThreadContextChanges:) name:NSManagedObjectContextDidSaveNotification object:nil];
#if TARGET_OS_IPHONE
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(save) name:UIApplicationWillTerminateNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(save) name:UIApplicationWillResignActiveNotification object:nil];
#elif TARGET_OS_MAC
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(save) name:NSApplicationWillTerminateNotification object:nil];
//[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(save) name:NSApplicationWillResignActiveNotification object:nil];
#endif
return self;
}
- (void)dealloc;
{
[[NSNotificationCenter defaultCenter] removeObserver:self];
[self reset];
[identifier release];
[managedObjectModelPath release];
[rootDirectory release];
[observerInfo release];
[super dealloc];
}
#pragma mark Accessors
- (void)setIdentifier:(NSString *)inIdentifier;
{
if ([inIdentifier isEqualToString:identifier]) {
return;
}
[inIdentifier retain];
[identifier release];
identifier = inIdentifier;
[self _updatePersistentStorePath];
[self reset];
}
- (void)setManagedObjectModelPath:(NSString *)inManagedObjectModelPath;
{
if (managedObjectModelPath && ![inManagedObjectModelPath isEqualToString:managedObjectModelPath]) {
[self reset];
}
[inManagedObjectModelPath retain];
[managedObjectModelPath release];
managedObjectModelPath = inManagedObjectModelPath;
}
- (void)setRootDirectory:(NSString *)inRootDirectory;
{
if ([inRootDirectory isEqualToString:rootDirectory]) {
return;
}
[inRootDirectory retain];
[rootDirectory retain];
rootDirectory = inRootDirectory;
[self _updatePersistentStorePath];
[self reset];
}
- (NSManagedObjectModel *)managedObjectModel;
{
if (managedObjectModel) {
return managedObjectModel;
}
if (self.managedObjectModelPath.length && [[NSFileManager defaultManager] fileExistsAtPath:self.managedObjectModelPath isDirectory:NULL]) {
managedObjectModel = [[NSManagedObjectModel alloc] initWithContentsOfURL:[NSURL fileURLWithPath:self.managedObjectModelPath]];
} else {
managedObjectModel = [[NSManagedObjectModel mergedModelFromBundles:nil] retain];
}
return managedObjectModel;
}
- (NSPersistentStoreCoordinator *)persistentStoreCoordinator;
{
if (persistentStoreCoordinator) {
return persistentStoreCoordinator;
}
if (!self.persistentStorePath.length || !self.managedObjectModel) {
return nil;
}
persistentStoreCoordinator = [[self _persistentStoreCoordinatorForPath:self.persistentStorePath] retain];
return persistentStoreCoordinator;
}
- (void)setMergePolicy:(NSMergePolicy *)inMergePolicy;
{
mergePolicy = inMergePolicy;
self.mainContext.mergePolicy = inMergePolicy;
}
- (dispatch_queue_t)workerQueue;
{
if (!workerQueue) {
NSString *workerName = [NSString stringWithFormat:@"com.systemoftouch.%@.WorkerQueue", NSStringFromClass([self class])];
workerQueue = dispatch_queue_create([workerName UTF8String], NULL);
}
return workerQueue;
}
- (STTargetActionQueue *)observerInfo;
{
if (observerInfo == NULL) {
observerInfo = [[STTargetActionQueue alloc] init];
}
return observerInfo;
}
#pragma mark Core Data Boilerplate
- (NSManagedObjectContext *)mainContext;
{
return [self _contextForThread:[NSThread mainThread]];
}
- (NSManagedObjectContext *)threadContext;
{
return [self _contextForThread:[NSThread currentThread]];
}
- (NSManagedObjectContext *)_contextForThread:(NSThread *)inThread;
{
NSMutableDictionary *threadDictionary = inThread.threadDictionary;
NSString *currentThreadContextKey = [self _currentThreadContextKey];
NSManagedObjectContext *threadContext = [threadDictionary objectForKey:currentThreadContextKey];
if (threadContext && threadContext.persistentStoreCoordinator != persistentStoreCoordinator) {
[threadDictionary removeObjectForKey:currentThreadContextKey];
threadContext = nil;
}
if (!threadContext) {
threadContext = [self createManagedObjectContext];
if (!threadContext) {
return nil;
}
[threadDictionary setObject:threadContext forKey:currentThreadContextKey];
}
if (inThread == [NSThread mainThread]) {
threadContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy;
} else {
threadContext.mergePolicy = self.mergePolicy;
}
return threadContext;
}
- (NSString *)_currentThreadContextKey;
{
return [NSString stringWithFormat:@"%@-%@", STDataStoreControllerThreadContextKey, NSStringFromClass([self class])];
}
- (NSPersistentStoreCoordinator *)_persistentStoreCoordinatorForPath:(NSString *)inPath;
{
// Create the directory for the persistent store if it doesn't exist yet.
if (![[NSFileManager defaultManager] fileExistsAtPath:self.rootDirectory]) {
[[NSFileManager defaultManager] recursivelyCreatePath:self.rootDirectory];
}
// Set up the persistent store coordinator.
persistentStoreCoordinator = [[[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:self.managedObjectModel] autorelease];
NSDictionary *options = @{NSMigratePersistentStoresAutomaticallyOption:@YES,
NSInferMappingModelAutomaticallyOption:@YES};
NSURL *storeURL = [NSURL fileURLWithPath:self.persistentStorePath];
NSError *error = nil;
if (![persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:options error:&error]) {
// If we couldn't load the persistent store (likely due to database incompatibility)
// delete the existing database and try again.
[self deletePersistentStore];
if (![persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:nil error:&error]) {
NSLog(@"Could not load database after clearing! Error: %@, %@", error, [error userInfo]);
exit(1);
}
}
return persistentStoreCoordinator;
}
#pragma mark Public Methods
- (void)save; {
NSError *error = nil;
[[self threadContext] save:&error];
if (error) {
NSLog(@"STDataStoreController Save Exception: %@", error);
}
}
#pragma mark Private Methods
- (NSManagedObjectContext *)createManagedObjectContext;
{
NSPersistentStoreCoordinator *coordinator = self.persistentStoreCoordinator;
if (!coordinator) {
return nil;
}
NSManagedObjectContext *newContext = [[[NSManagedObjectContext alloc] init] autorelease];
[newContext setPersistentStoreCoordinator:coordinator];
return newContext;
}
- (void)reset;
{
// Force the queue to complete before releasing the queue
// by waiting until an empty block finishes
if (workerQueue) {
dispatch_release(workerQueue);
workerQueue = NULL;
}
// Release the MOM and PSC. They'll get recreated using the
// current settings next time they're needed.
[managedObjectModel release];
managedObjectModel = nil;
[persistentStoreCoordinator release];
persistentStoreCoordinator = nil;
[[NSThread mainThread].threadDictionary removeObjectForKey:[self _currentThreadContextKey]];
}
- (void)deletePersistentStore;
{
// Clear out Core Data stack
[self reset];
// Remove persistent store file
[self deletePersistentStoreForIdentifier:self.identifier];
}
- (void)deletePersistentStoreForIdentifier:(NSString *)inIdentifier;
{
if (!inIdentifier.length) {
return;
}
BOOL deletingExisting = (!self.identifier || [inIdentifier isEqualToString:self.identifier]);
if (deletingExisting) {
[[NSNotificationCenter defaultCenter] postNotificationName:STDataStoreControllerWillClearDatabaseNotification object:self];
}
[[NSFileManager defaultManager] removeItemAtPath:[self persistentStorePathForIdentifier:inIdentifier] error:NULL];
if (deletingExisting) {
[[NSNotificationCenter defaultCenter] postNotificationName:STDataStoreControllerDidClearDatabaseNotification object:self];
}
}
- (NSString *)persistentStorePathForIdentifier:(NSString *)inIdentifier;
{
if (!self.rootDirectory.length || !self.identifier.length) {
return nil;
}
return [self.rootDirectory stringByAppendingPathComponent:[self.identifier stringByAppendingPathExtension:@"sqlite"]];
}
- (void)_mergeThreadContextChanges:(NSNotification *)inNotification;
{
if (![NSThread isMainThread]) {
[self performSelectorOnMainThread:@selector(_mergeThreadContextChanges:)
withObject:inNotification waitUntilDone:NO];
return;
}
NSManagedObjectContext *changedContext = inNotification.object;
if (changedContext.persistentStoreCoordinator != self.persistentStoreCoordinator) {
return;
}
[self.mainContext mergeChangesFromContextDidSaveNotification:inNotification];
[self processChangesMergedToMainContext];
// Check to see if the changes include updates to any
// entity types currently being observed
NSSet *insertedObjects = [inNotification.userInfo objectForKey:NSInsertedObjectsKey];
NSSet *updatedObjects = [inNotification.userInfo objectForKey:NSUpdatedObjectsKey];
NSSet *deletedObjects = [inNotification.userInfo objectForKey:NSDeletedObjectsKey];
NSArray *entityKeys = [self.observerInfo allKeys];
for (NSString *currentEntityKey in entityKeys) {
NSPredicate *keyPredicate = [NSPredicate predicateWithFormat:@"entity.name == %@", currentEntityKey];
NSSet *filteredInsertedObjects = [insertedObjects filteredSetUsingPredicate:keyPredicate];
NSSet *filteredUpdatedObjects = [updatedObjects filteredSetUsingPredicate:keyPredicate];
NSSet *filteredDeletedObjects = [deletedObjects filteredSetUsingPredicate:keyPredicate];
if (!filteredInsertedObjects.count && !filteredUpdatedObjects.count && !filteredDeletedObjects.count) {
continue;
}
NSMutableDictionary *userInfo = [[[NSMutableDictionary alloc] init] autorelease];
if (filteredInsertedObjects) {
[userInfo setObject:filteredInsertedObjects forKey:NSInsertedObjectsKey];
}
if (filteredUpdatedObjects) {
[userInfo setObject:filteredUpdatedObjects forKey:NSUpdatedObjectsKey];
}
if (filteredDeletedObjects) {
[userInfo setObject:filteredDeletedObjects forKey:NSDeletedObjectsKey];
}
[self.observerInfo performActionsForKey:currentEntityKey withObject:userInfo];
}
}
- (void)_updatePersistentStorePath;
{
self.persistentStorePath = [self persistentStorePathForIdentifier:self.identifier];
}
#pragma mark Methods for Subclassing
- (void)processChangesMergedToMainContext;
{
}
#pragma mark Change Observation
- (void)addObserver:(id)inObserver action:(SEL)inAction forEntityName:(NSString *)inEntityName;
{
[self.observerInfo addTarget:inObserver action:inAction forKey:inEntityName];
}
- (void)removeObserver:(id)inObserver forEntityName:(NSString *)inEntityName;
{
[self.observerInfo removeTarget:inObserver forKey:inEntityName];
}
- (void)removeObserver:(id)inObserver;
{
[self.observerInfo removeTarget:inObserver];
}
@end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment