Created
September 17, 2014 07:47
-
-
Save kwent/51ca208c9cfd4cc47817 to your computer and use it in GitHub Desktop.
STDataStoreController
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// 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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// 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