https://blog.codecentric.de/en/2014/11/concurrency-coredata/
Everything on Main Queue
Create a iOS project and check “using Core Data”.
This is how it works:
At the most basic level we have a CoreDataStack class.
In its implementation file, we first have:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
@interface CoreDataStack : NSObject { } +(instancetype) defaultStack; //instantiations of self MUST accesses this class method. //no other instantiations of this method is possible. Thus we ensure 1 entry point. //public @property (readonly, strong, nonatomic) NSManagedObjectContext *managedObjectContext; @property (readonly, strong, nonatomic) NSManagedObjectModel *managedObjectModel; @property (readonly, strong, nonatomic) NSPersistentStoreCoordinator *persistentStoreCoordinator; - (void)saveContext; - (NSURL *)applicationDocumentsDirectory; @end |
NSManagedObjectModel – Managed Object Model
We start with the managed object model.
- Essentially, it is the schema of our model.
- Our model comes from the .momd file. In xcode, you can create your model and it will save to the .momd file.
- Then we load it into a NSURL, which in turn is used to initialize a NSManagedObjectModel object.
1 2 3 4 5 6 7 8 9 10 11 12 |
- (NSManagedObjectModel *)managedObjectModel { NSLog(@"(NSManagedObjectModel *)managedObjectModel - database schema, uses .momd file to create object model"); // The managed object model for the application. It is a fatal error for the application not to be able to find and load its model. if (_managedObjectModel != nil) { return _managedObjectModel; } NSURL *modelURL = [[NSBundle mainBundle] URLForResource:@"CoreData2" withExtension:@"momd"]; _managedObjectModel = [[NSManagedObjectModel alloc] initWithContentsOfURL:modelURL]; return _managedObjectModel; } |
NSPersistentStoreCoordinator – Persistent Store Coordinator
The persistent store coordinator is responsible for coordinating access to multiple persistent object stores. As an iOS developer you will never directly interact with the persistent store coordinator and, in fact, will very rarely need to develop an application that requires more than one persistent object store. When multiple stores are required, the coordinator presents these stores to the upper layers of the Core Data stack as a single store.
This is essentially the database connection. It coordinates between our model and a .sqlite file.
The class has attribute that points to our previous defined NSManagedObjectModel. Then it stores a NSURL to a .sqlite file. From there on, it is used by either one or many NSManagedObjectContexts in order to do DB operations.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
- (NSPersistentStoreCoordinator *)persistentStoreCoordinator { NSLog(@"(NSPersistentStoreCoordinator *) persistentStoreCoordinator - database connection, coordinates between our object model and .sqlite file. Our connection to the .sqlite file" ); // The persistent store coordinator for the application. This implementation creates and return a coordinator, having added the store for the application to it. if (_persistentStoreCoordinator != nil) { return _persistentStoreCoordinator; } // Create the coordinator and store _persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:[self managedObjectModel]]; NSURL *storeURL = [[self applicationDocumentsDirectory] URLByAppendingPathComponent:@"CoreData2.sqlite"]; NSError *error = nil; NSString *failureReason = @"There was an error creating or loading the application's saved data."; if (![_persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:nil error:&error]) { ....... abort(); } return _persistentStoreCoordinator; } |
NSManagedObjectContext – managed object context
This is the scratch pad for what goes into and comes out of the database. We use this to do many operations, then use save context, predicates, and other special operations to make changes to the database. There can be either 1 or more context used at the same time.
Full Source
CoreDataStack.h
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
#import <Foundation/Foundation.h> #import <CoreData/CoreData.h> @class Person; @interface CoreDataStack : NSObject { } +(instancetype) defaultStack; //instantiations of self MUST accesses this class method. //no other instantiations of this method is possible. Thus we ensure 1 entry point. //public @property (readonly, strong, nonatomic) NSManagedObjectContext *managedObjectContext; @property (readonly, strong, nonatomic) NSManagedObjectModel *managedObjectModel; @property (readonly, strong, nonatomic) NSPersistentStoreCoordinator *persistentStoreCoordinator; - (void)saveContext; - (NSURL *)applicationDocumentsDirectory; @end |
CoreDataStack.m
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 |
@implementation CoreDataStack @synthesize managedObjectContext = _managedObjectContext; @synthesize managedObjectModel = _managedObjectModel; @synthesize persistentStoreCoordinator = _persistentStoreCoordinator; -(void)reportOnAllPeopleToLog { NSLog(@"------- logging out results --------------"); //Get all the projects in the data store NSFetchRequest *request = [[NSFetchRequest alloc] init]; NSEntityDescription *entity = [NSEntityDescription entityForName:@"Person" inManagedObjectContext:[self managedObjectContext]]; request.entity = entity; NSArray *listOfPeople = [[self managedObjectContext] executeFetchRequest:request error:nil]; //List out contents of each project if([listOfPeople count] == 0) NSLog(@"There are no peple in the data store yet"); else { NSLog(@"HERE ARE THE People IN THE DATA STORE"); [listOfPeople enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) { NSLog(@"%lu ----------------", idx); NSLog(@"firstName = %@", [obj firstName]); NSLog(@"lastName = %@", [obj lastName]); //[[self managedObjectContext] deleteObject:obj]; //[[self managedObjectContext] save:nil]; }]; } } +(instancetype)defaultStack { //all instantiations of THCoreDataStack, will access this method //we use this one and only statick variable static CoreDataStack * defaultStack; //NSLog(@"address of defaultStack - %p", defaultStack); if(!defaultStack) { static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ //here, code gets executed once //hence we alloc our one and only defaultStack variable once defaultStack = [[self alloc] init]; //NSLog(@"ALLOC defaultStack on GCD - %p", defaultStack); }); } return defaultStack; } - (NSURL *)applicationDocumentsDirectory { // The directory the application uses to store the Core Data store file. This code uses a directory named "rickytsao.DataCoreTut" in the application's documents directory. return [[[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] lastObject]; } - (NSManagedObjectModel *)managedObjectModel { NSLog(@"(NSManagedObjectModel *)managedObjectModel - database schema, uses .momd file to create object model"); // The managed object model for the application. It is a fatal error for the application not to be able to find and load its model. if (_managedObjectModel != nil) { return _managedObjectModel; } NSURL *modelURL = [[NSBundle mainBundle] URLForResource:@"CoreData2" withExtension:@"momd"]; _managedObjectModel = [[NSManagedObjectModel alloc] initWithContentsOfURL:modelURL]; return _managedObjectModel; } - (NSPersistentStoreCoordinator *)persistentStoreCoordinator { NSLog(@"(NSPersistentStoreCoordinator *) persistentStoreCoordinator - database connection, coordinates between our object model and .sqlite file. Our connection to the .sqlite file" ); // The persistent store coordinator for the application. This implementation creates and return a coordinator, having added the store for the application to it. if (_persistentStoreCoordinator != nil) { return _persistentStoreCoordinator; } // Create the coordinator and store _persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:[self managedObjectModel]]; NSURL *storeURL = [[self applicationDocumentsDirectory] URLByAppendingPathComponent:@"CoreData2.sqlite"]; NSError *error = nil; NSString *failureReason = @"There was an error creating or loading the application's saved data."; if (![_persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:nil error:&error]) { // Report any error we got. NSMutableDictionary *dict = [NSMutableDictionary dictionary]; dict[NSLocalizedDescriptionKey] = @"Failed to initialize the application's saved data"; dict[NSLocalizedFailureReasonErrorKey] = failureReason; dict[NSUnderlyingErrorKey] = error; error = [NSError errorWithDomain:@"YOUR_ERROR_DOMAIN" code:9999 userInfo:dict]; // Replace this with code to handle the error appropriately. // abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development. NSLog(@"Unresolved error %@, %@", error, [error userInfo]); abort(); } return _persistentStoreCoordinator; } - (NSManagedObjectContext *)managedObjectContext { NSLog(@"[self managedObjectContext] called, (NSManagedObjectContext *)managedObjectContext - scratch pad for objects that come from the database" ); // Returns the managed object context for the application (which is already bound to the persistent store coordinator for the application.) if (_managedObjectContext != nil) { return _managedObjectContext; } NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator]; if (!coordinator) { return nil; } _managedObjectContext = [[NSManagedObjectContext alloc] init]; [_managedObjectContext setPersistentStoreCoordinator:coordinator]; return _managedObjectContext; } #pragma mark - Core Data Saving support - (void)saveContext { NSLog(@" --- saveContext --- " ); NSManagedObjectContext *managedObjectContext = self.managedObjectContext; if (managedObjectContext != nil) { NSError *error = nil; if ([managedObjectContext hasChanges] && ![managedObjectContext save:&error]) { // Replace this implementation with code to handle the error appropriately. // abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development. NSLog(@"Unresolved error %@, %@", error, [error userInfo]); abort(); } } } |
Inserting into Core Data
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
-(BOOL)addBulkPeople { NSLog(@"-------- MAKE NEW PERSON -----------"); for(int i = 0; i < 10000; i++) { NSLog(@"adding %d", i); CoreDataStack * coreDataStack = [CoreDataStack defaultStack]; //insert new entry into core data environment Person * aPerson = [NSEntityDescription insertNewObjectForEntityForName:@"Person" inManagedObjectContext:coreDataStack.managedObjectContext]; aPerson.firstName = @"Date and Time: "; NSTimeInterval today = [[NSDate date] timeIntervalSince1970]; aPerson.lastName = [NSString stringWithFormat:@"%f", today]; NSLog(@"adding object with address: %p", aPerson); [coreDataStack saveContext]; } return TRUE; } @end |
Notice this method:
1 2 |
Person * aPerson = [NSEntityDescription insertNewObjectForEntityForName:@"Person" inManagedObjectContext:coreDataStack.managedObjectContext]; |
insertNewObjectForEntityForName returns a NSManagedObject * for you to manipulate. All you have to do is set the attributes of the object and then call the saveContext method.
NSManagedObject are the objects that are created by your application code to store data. A managed object can be considered as a row or a record in a relational database table. For each new record to be added, a new managed object must be created to store the data. Similarly, retrieved data will be returned in the form of managed objects, one for each record matching the defined retrieval criteria. Managed objects are actually instances of the NSManagedObject class, or a subclass thereof. These objects are contained and maintained by the managed object context.
When NSEntityDescription returns an object for you to insert. All you have to do is set the attributes of the object and then call the saveContext method.
Every object that is returned to you by the method insertNewObjectForEntityForName will be monitored by your context. Once you finish updating those objects, and you call saveContext, your context will check up on those objects and then save them accordingly.
Group changes together
However, calling saveContext for every change takes a bit longer because you are having the persistent store coordinator communicate with the database off of one change. You can group your changes together, and then tell your context to save once.
The Downside
While this stack is very simplistic, the downside is that if you record large amount of data, the UI will be unresponsive because you are running off of the main thread.
For example, in our example, we have a for loop of 10. Change it to 10,000, and try to play with the UI controls on your viewController. They won’t work because your main thread is busy processing the 10,000 core data operations.