Created
May 2, 2013 14:54
-
-
Save leeprobert/5502774 to your computer and use it in GitHub Desktop.
Core Data class categories to extend the functionality. There are convenience methods for grabbing ALL objects of a particular entity, or via predicate. Also a convenient and safe way of setting attribute values from a JSON object with the same key names. Credit to CoreDataStack code : https://github.com/adamgit/CoreDataStack
and to Tom Harringt…
This file contains hidden or 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
| /** | |
| CoreDataStack - CoreData made easy | |
| c.f. https://github.com/adamgit/CoreDataStack for docs + support | |
| */ | |
| #import <Foundation/Foundation.h> | |
| #import <CoreData/CoreData.h> | |
| #define kNotificationDestroyAllNSFetchedResultsControllers @"DestroyAllNSFetchedResultsControllers" | |
| #define kCoreDataStackDefaultModelName @"EngagerContactsBrowser" | |
| typedef enum CDSStoreType | |
| { | |
| CDSStoreTypeUnknown, | |
| CDSStoreTypeXML, | |
| CDSStoreTypeSQL, | |
| CDSStoreTypeBinary, | |
| CDSStoreTypeInMemory | |
| } CDSStoreType; | |
| @interface CoreDataStack : NSObject | |
| { | |
| NSManagedObjectModel* _mom; | |
| NSManagedObjectContext* _moc; | |
| NSPersistentStoreCoordinator* _psc; | |
| } | |
| /*! The actual URL that's in use - you can pass this to init, or let the CoreDataStack work it out automatically */ | |
| @property(nonatomic,retain) NSURL* databaseURL; | |
| /*! Name of the model file in Xcode, with no extension - e.g. for "Model.xcdatamodeld", this is "Model" */ | |
| @property(nonatomic,retain) NSString* modelName; | |
| /*! Apple's source code is weak and crashes if you don't tell it the correct 'type' of CoreData store. This class will try to guess it for you - or you can explicitly set it during or after init */ | |
| @property(nonatomic) CDSStoreType coreDataStoreType; | |
| /*! To use CoreData, you need to provide a permanent filename for it to save its sqlite DB to disk - or provide a manual URL to where | |
| you've already saved it. You also need to provide a model name | |
| I recommend "MyModelName.sqlite" as a name | |
| Returns a SHARED stack - multiple classes fetching the same filename will get the same stack object back (this is what you want in 99.9% of cases) | |
| TODO: allow non-shared stacks | |
| If you need separate stacks, then init the first one using the name, and init all subsequent ones like this: | |
| firstStack = [CoreDataStack coreDataStackWithDatabaseFilename: @"MyModel"]; | |
| secondStack = [CoreDataStack coreDataStackWithDatabaseURL: firstStack.databaseURL]; // uses the same config data, but is NOT shared | |
| */ | |
| +(CoreDataStack*) coreDataStackWithModelName:(NSString*) mname; | |
| /*! To use CoreData, you need to provide a permanent filename for it to save its sqlite DB to disk - or provide a manual URL to where | |
| you've already saved it. */ | |
| +(CoreDataStack*) coreDataStackWithModelName:(NSString *)mname databaseFilename:(NSString*) dbname; | |
| /*! To use CoreData, you need to provide a permanent filename for it to save its sqlite DB to disk - or provide a manual URL to where | |
| you've already saved it. | |
| */ | |
| +(CoreDataStack*) coreDataStackWithDatabaseURL:(NSURL*) dburl modelName:(NSString*) mname; | |
| /* | |
| Here's a static just for this project | |
| */ | |
| +(CoreDataStack*) defaultStack; | |
| - (NSFetchedResultsController *)contactEntityFetchedResultsController; | |
| - (id)initWithURL:(NSURL*) url modelName:(NSString *)mname storeType:(CDSStoreType) type; | |
| -(NSManagedObjectModel*) dataModel; | |
| -(NSPersistentStoreCoordinator*) persistentStoreCoordinator; | |
| -(NSManagedObjectContext*) managedObjectContext; | |
| #pragma mark - essential methods that Apple forgot to provide | |
| /*! Apple's implementation of CoreData doesn't support Blocks. How sad. Let's fix that for them! */ | |
| -(void) saveOrFail:(void(^)(NSError* errorOrNil)) blockFailedToSave; | |
| /*! Deletes all data from your CoreData store - this is a very fast (and allegedly safe) way | |
| of resetting your data to a "virginal" state, as if your app had just been installed for the | |
| first time. | |
| It is ALMOST CERTAINLY not safe to call from a multithreaded environment, because nothing in | |
| Apple's code is threadsafe. But you already knew that, right? | |
| c.f. http://stackoverflow.com/questions/1077810/delete-reset-all-entries-in-core-data | |
| ALSO ... side effect: all NSFetchedResultsController's will explode. As of iOS 5.1, NSFetchedResultsController is | |
| still an inherently buggy class, and I'd recommend avoid using it if you possibly can. To make this slightly | |
| less painful, we'll post an NSNotificaiton that you can listen for inside your own NSFetchedResultsController | |
| subclasses, and re-build your NSFetchedResultsController when you receive one. | |
| Easy way: use this code as your init method for your NSFetchedResultsController subclass | |
| -(id)initWithCoder:(NSCoder *)aDecoder | |
| { | |
| self = [super initWithCoder:aDecoder]; | |
| if (self) { | |
| // Custom initialization | |
| [[NSNotificationCenter defaultCenter] addObserverForName:kNotificationDestroyAllNSFetchedResultsControllers object:nil queue:nil usingBlock:^(NSNotification *note) { | |
| NSLog(@"[%@] must destroy my nsfetchedresultscontroller", [self class]); | |
| [__fetchedResultsController release]; | |
| __fetchedResultsController = nil; | |
| }]; | |
| } | |
| return self; | |
| } | |
| */ | |
| -(void) wipeAllData; | |
| @end |
This file contains hidden or 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
| /** | |
| CoreDataStack - CoreData made easy | |
| c.f. https://github.com/adamgit/CoreDataStack for docs + support | |
| */ | |
| #import "CoreDataStack.h" | |
| static CoreDataStack *_instance = nil; | |
| @interface CoreDataStack() | |
| + (NSURL *)applicationDocumentsDirectory; | |
| @end | |
| @implementation CoreDataStack | |
| #pragma mark - Fetch tools | |
| - (NSFetchedResultsController *)contactEntityFetchedResultsController | |
| { | |
| NSManagedObjectContext *context = self.managedObjectContext; | |
| NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init]; | |
| // Edit the entity name as appropriate. | |
| NSEntityDescription *entity = [NSEntityDescription entityForName:@"Person" inManagedObjectContext:context]; | |
| [fetchRequest setEntity:entity]; | |
| // Set the batch size to a suitable number. | |
| [fetchRequest setFetchBatchSize:20]; | |
| // Edit the sort key as appropriate. | |
| NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:@"firstName" ascending:NO]; | |
| NSArray *sortDescriptors = @[sortDescriptor]; | |
| [fetchRequest setSortDescriptors:sortDescriptors]; | |
| // Edit the section name key path and cache name if appropriate. | |
| // nil for section name key path means "no sections". | |
| NSFetchedResultsController *aFetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:context sectionNameKeyPath:nil cacheName:@"Master"]; | |
| return aFetchedResultsController; | |
| } | |
| #pragma mark - Static methods | |
| +(CoreDataStack*) coreDataStackWithModelName:(NSString *)mname databaseFilename:(NSString*) dbname | |
| { | |
| __block NSURL *storeURL; | |
| static dispatch_once_t onceToken; | |
| dispatch_once(&onceToken, ^{ | |
| if( dbname != nil ) | |
| storeURL = [[self applicationDocumentsDirectory] URLByAppendingPathComponent:dbname]; | |
| else | |
| storeURL = [[self applicationDocumentsDirectory] URLByAppendingPathComponent:mname]; | |
| _instance = [[CoreDataStack alloc] initWithURL: storeURL | |
| modelName: mname | |
| storeType: CDSStoreTypeUnknown]; | |
| }); | |
| return _instance; | |
| } | |
| +(CoreDataStack*) coreDataStackWithModelName:(NSString *)mname | |
| { | |
| static dispatch_once_t onceToken; | |
| dispatch_once(&onceToken, ^{ | |
| _instance = [self coreDataStackWithModelName:mname databaseFilename:nil]; | |
| }); | |
| return _instance; | |
| } | |
| +(CoreDataStack*) coreDataStackWithDatabaseURL:(NSURL*) dburl modelName:(NSString *)mname | |
| { | |
| static dispatch_once_t onceToken; | |
| dispatch_once(&onceToken, ^{ | |
| _instance = [[CoreDataStack alloc] initWithURL:dburl modelName:mname storeType:CDSStoreTypeUnknown]; | |
| }); | |
| return _instance; | |
| } | |
| +(CoreDataStack*) defaultStack { | |
| static dispatch_once_t onceToken; | |
| dispatch_once(&onceToken, ^{ | |
| _instance = [self coreDataStackWithModelName:kCoreDataStackDefaultModelName databaseFilename:nil]; | |
| }); | |
| return _instance; | |
| } | |
| #pragma mark - public | |
| -(void) guessStoreType:(NSString*) fileExtension | |
| { | |
| if( fileExtension != nil && [fileExtension length] > 0) | |
| { | |
| /** Guess the Store Type */ | |
| if( [@"BINARY" isEqualToString:[fileExtension uppercaseString]] ) | |
| { | |
| self.coreDataStoreType = CDSStoreTypeBinary; | |
| } | |
| else if( [@"XML" isEqualToString:[fileExtension uppercaseString]] ) | |
| { | |
| self.coreDataStoreType = CDSStoreTypeXML; | |
| } | |
| else if( [@"SQL" isEqualToString:[fileExtension uppercaseString]] ) | |
| { | |
| self.coreDataStoreType = CDSStoreTypeSQL; | |
| } | |
| else if( [@"SQLITE" isEqualToString:[fileExtension uppercaseString]] ) | |
| { | |
| self.coreDataStoreType = CDSStoreTypeSQL; | |
| } | |
| else | |
| NSLog(@"[%@] WARN: no explicit store type given, and could NOT guess the store type. Core Data will PROBABLY refuse to initialize!", [self class] ); | |
| } | |
| } | |
| - (id)initWithURL:(NSURL*) url modelName:(NSString *)mname storeType:(CDSStoreType) type | |
| { | |
| self = [super init]; | |
| if (self) { | |
| self.databaseURL = url; | |
| self.modelName = mname; | |
| self.coreDataStoreType = type; | |
| if( self.coreDataStoreType == CDSStoreTypeUnknown ) | |
| { | |
| [self guessStoreType:[self.databaseURL pathExtension]]; | |
| } | |
| } | |
| return self; | |
| } | |
| -(NSManagedObjectModel*) dataModel | |
| { | |
| if( _mom == nil ) | |
| { | |
| NSString* momdPath = [[NSBundle mainBundle] pathForResource:self.modelName ofType:@"momd"]; | |
| NSURL* momdURL = [NSURL fileURLWithPath:momdPath]; | |
| _mom = [[NSManagedObjectModel alloc] initWithContentsOfURL:momdURL]; | |
| } | |
| return _mom; | |
| } | |
| /** | |
| Returns the URL to the application's Documents directory. | |
| */ | |
| + (NSURL *)applicationDocumentsDirectory { | |
| return [[[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] lastObject]; | |
| } | |
| -(NSPersistentStoreCoordinator*) persistentStoreCoordinator | |
| { | |
| if( _psc == nil ) | |
| { | |
| NSAssert( self.databaseURL != nil, @"This class should have been init'd with a URL, or with enough info to construct a valid URL" ); | |
| NSError *error = nil; | |
| _psc = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:[self dataModel]]; | |
| NSString* storeType; | |
| switch( self.coreDataStoreType ) | |
| { | |
| case CDSStoreTypeXML: | |
| { | |
| #ifdef NSXMLStoreType | |
| storeType = NSXMLStoreType; | |
| #else | |
| NSAssert( FALSE, @"Apple does not allow you to use an XML store on this OS. Only available on OS X" ); | |
| #endif | |
| }break; | |
| case CDSStoreTypeBinary: | |
| { | |
| storeType = NSBinaryStoreType; | |
| }break; | |
| case CDSStoreTypeUnknown: | |
| { | |
| storeType = NSSQLiteStoreType; | |
| NSLog(@"[%@] WARN: unknown store type. Guessing ... %@ ??", [self class], storeType ); | |
| }break; | |
| case CDSStoreTypeSQL: | |
| { | |
| storeType = NSSQLiteStoreType; | |
| }break; | |
| case CDSStoreTypeInMemory: | |
| { | |
| storeType = NSInMemoryStoreType; | |
| }break; | |
| } | |
| if (![_psc addPersistentStoreWithType:storeType configuration:nil URL:self.databaseURL options:nil error:&error]) { | |
| /* | |
| Replace this implementation with code to handle the error appropriately. | |
| */ | |
| NSLog(@"[%@] Unresolved error %@, %@", [self class], error, [error userInfo]); | |
| abort(); | |
| } | |
| } | |
| return _psc; | |
| } | |
| -(NSManagedObjectContext*) managedObjectContext | |
| { | |
| if( _moc == nil ) | |
| { | |
| _moc = [[NSManagedObjectContext alloc] init]; | |
| [_moc setPersistentStoreCoordinator:[self persistentStoreCoordinator]]; | |
| } | |
| return _moc; | |
| } | |
| -(void) saveOrFail:(void(^)(NSError* errorOrNil)) blockFailedToSave | |
| { | |
| NSError* error = nil; | |
| if( [[self managedObjectContext] save:&error] ) | |
| { | |
| return; | |
| } | |
| else | |
| { | |
| if( [self managedObjectContext] == nil ) | |
| { | |
| blockFailedToSave( [NSError errorWithDomain:@"CoreDataStack" code:1 userInfo:[NSDictionary dictionaryWithObjectsAndKeys:@"Attempted to save a nil NSManagedObjectContext. This CoreDataStack has no context - probably there was an earlier error trying to access the CoreData database file", NSLocalizedDescriptionKey, nil]] ); | |
| } | |
| else | |
| { | |
| blockFailedToSave( error ); | |
| } | |
| } | |
| } | |
| -(void) wipeAllData | |
| { | |
| for( NSPersistentStore* store in [self.persistentStoreCoordinator persistentStores] ) | |
| { | |
| NSError *error; | |
| NSURL *storeURL = store.URL; | |
| [self.persistentStoreCoordinator removePersistentStore:store error:&error]; | |
| [[NSFileManager defaultManager] removeItemAtPath:storeURL.path error:&error]; | |
| _moc = nil; | |
| _mom = nil; | |
| _psc = nil; | |
| /** ... side effect: all NSFetchedResultsController's will now explode because Apple didn't code them very well */ | |
| [[NSNotificationCenter defaultCenter] postNotificationName:kNotificationDestroyAllNSFetchedResultsControllers object:self]; | |
| } | |
| } | |
| @end |
This file contains hidden or 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
| // | |
| // NSManagedObject+safeSetValuesKeysWithDictionary.h | |
| // | |
| #import <CoreData/CoreData.h> | |
| @interface NSManagedObject (safeSetValuesKeysWithDictionary) | |
| - (void)safeSetValuesForKeysWithDictionary:(NSDictionary *)keyedValues dateFormatter:(NSDateFormatter *)dateFormatter; | |
| @end |
This file contains hidden or 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
| // | |
| // NSManagedObject+safeSetValuesKeysWithDictionary.m | |
| // | |
| // Tom Harrington : http://www.cimgf.com/2011/06/02/saving-json-to-core-data/ | |
| #import "NSManagedObject+safeSetValuesKeysWithDictionary.h" | |
| @implementation NSManagedObject (safeSetValuesKeysWithDictionary) | |
| - (void)safeSetValuesForKeysWithDictionary:(NSDictionary *)keyedValues dateFormatter:(NSDateFormatter *)dateFormatter | |
| { | |
| NSDictionary *attributes = [[self entity] attributesByName]; | |
| for (NSString *attribute in attributes) { | |
| id value = [keyedValues objectForKey:attribute]; | |
| if (value == nil) { | |
| continue; | |
| } | |
| NSAttributeType attributeType = [[attributes objectForKey:attribute] attributeType]; | |
| if ((attributeType == NSStringAttributeType) && ([value isKindOfClass:[NSNumber class]])) { | |
| value = [value stringValue]; | |
| } else if (((attributeType == NSInteger16AttributeType) || (attributeType == NSInteger32AttributeType) || (attributeType == NSInteger64AttributeType) || (attributeType == NSBooleanAttributeType)) && ([value isKindOfClass:[NSString class]])) { | |
| value = [NSNumber numberWithInteger:[value integerValue]]; | |
| } else if ((attributeType == NSFloatAttributeType) && ([value isKindOfClass:[NSString class]])) { | |
| value = [NSNumber numberWithDouble:[value doubleValue]]; | |
| } else if ((attributeType == NSDateAttributeType) && ([value isKindOfClass:[NSString class]]) && (dateFormatter != nil)) { | |
| value = [dateFormatter dateFromString:value]; | |
| } | |
| [self setValue:value forKey:attribute]; | |
| } | |
| } | |
| @end |
This file contains hidden or 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
| // | |
| // NSManagedObjectContext+extended.h | |
| // | |
| // Created by Lee Probert on 02/05/2013. | |
| // Copyright (c) 2013 Agnitio. All rights reserved. | |
| // | |
| #import <CoreData/CoreData.h> | |
| typedef void (^SuccessBlockType)(NSArray* result); | |
| typedef void (^FailureBlockType)(NSError* error); | |
| @interface NSManagedObjectContext (extended) | |
| - (void) fetchAllObjectsWithEntityName:(NSString*)entity success:(SuccessBlockType)successBlock failure:(FailureBlockType)failureBlock; | |
| - (void) fetchObjectsWithPredicate:(NSPredicate*)predicate entityName:(NSString*)entity success:(SuccessBlockType)successBlock failure:(FailureBlockType)failureBlock; | |
| @end |
This file contains hidden or 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
| // | |
| // NSManagedObjectContext+extended.m | |
| // | |
| // Created by Lee Probert on 02/05/2013. | |
| // Copyright (c) 2013 Agnitio. All rights reserved. | |
| // | |
| #import "NSManagedObjectContext+extended.h" | |
| @implementation NSManagedObjectContext (extended) | |
| - (void) fetchAllObjectsWithEntityName:(NSString*)entity success:(SuccessBlockType)successBlock failure:(FailureBlockType)failureBlock{ | |
| NSError *error; | |
| NSFetchRequest *fetch = [[NSFetchRequest alloc] initWithEntityName:entity]; | |
| NSArray *result = [self executeFetchRequest:fetch error:&error]; | |
| if(!error && [result count]>0){ | |
| successBlock(result); | |
| }else{ | |
| failureBlock(error); | |
| } | |
| } | |
| - (void) fetchObjectsWithPredicate:(NSPredicate*)predicate entityName:(NSString*)entity success:(SuccessBlockType)successBlock failure:(FailureBlockType)failureBlock { | |
| NSError *error; | |
| NSFetchRequest *fetch = [[NSFetchRequest alloc] initWithEntityName:entity]; | |
| [fetch setPredicate:predicate]; | |
| NSArray *result = [self executeFetchRequest:fetch error:&error]; | |
| if(!error && [result count]>0){ | |
| successBlock(result); | |
| }else{ | |
| failureBlock(error); | |
| } | |
| } | |
| @end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment