- http://www.cocoabuilder.com/archive/cocoa/290810-core-data-conflict-detection.html
- http://joelparsons.net/blog/2013/09/02/background-core-data-with-privatequeuecontext
Have the context process in the background
First step is to add a private queue context where it will save a lot of data into the database.
- add a NSManagedObjectContext privateQueueContext object, synthesize it
- custom set method where we create the context with a queue
- queue up tasks such as bulk add
full source for CoreDataStack.h
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
#import <CoreData/CoreData.h> @class Person; @interface CoreDataStack : NSObject { } +(instancetype) defaultStack; @property (readonly, strong, nonatomic) NSManagedObjectContext * privateQueueContext; @property (readonly, strong, nonatomic) NSManagedObjectContext * mainQueueContext; @property (readonly, strong, nonatomic) NSManagedObjectModel * managedObjectModel; @property (readonly, strong, nonatomic) NSPersistentStoreCoordinator * persistentStoreCoordinator; -(instancetype)init; - (NSURL *)applicationDocumentsDirectory; -(BOOL)addBulkPeople; -(void)reportOnAllPeopleToLog; |
addBulkPeople method is the process of using our privateQueueContext object and throwing a block on the queue and processing the saving of data into the database.
reportOnAllPeopleToLog method is using our mainQueueContext to simply get data from CoreData and see what’s in the database.
CoreDataStack.m
First, we set up our init method and init our queue contexts via custom setter methods.
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 |
#import <CoreData/CoreData.h> #import "CoreDataStack.h" #import "Person.h" @implementation CoreDataStack //private queue context does job in the background @synthesize privateQueueContext = _privateQueueContext; -(instancetype)init { if(self=[super init]) { NSLog(@"Initializing CoreDataStack's Context(s), PSC, Model"); if(!self.privateQueueContext) { NSLog(@"private Queue Context not created"); } if(!self.mainQueueContext) { NSLog(@"Main Queue Context not created"); } return self; } return nil; } |
Notice when we alloc and init NSManagedObjectContext, we initialize the context with concurrency type NSPrivateQueueConcurrencyType
Essentially, instead of the old way of attaching a context to a thread, we now have the option of attaching a context to a queue. This means that whatever job and/or tasks are lined up for this context gets queued. Thus, whatever operation needs to be done by that context will be completed FIFO via blocks.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
- (NSManagedObjectContext *)privateQueueContext { // Returns the managed object context for the application // (which is already bound to the persistent store coordinator // for the application.) if (_privateQueueContext != nil) { return _privateQueueContext; } NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator]; if (!coordinator) { return nil; } _privateQueueContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType]; [_privateQueueContext setPersistentStoreCoordinator:coordinator]; NSLog(@"PrivateQueueContext CREATED" ); return _privateQueueContext; } |
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 |
#pragma mark - Core Data MAIN METHODS -(BOOL)addBulkPeople { NSLog(@"-------- MAKE NEW PERSON -----------"); //perform this block of code in the background [self.privateQueueContext performBlock:^{ for(int i = 0; i < 10000; i++) { CoreDataStack * coreDataStack = [CoreDataStack defaultStack]; //will return an object from the heap, so we can store it //insert new entry into core data environment Person * aPerson = [NSEntityDescription insertNewObjectForEntityForName:@"Person" inManagedObjectContext:coreDataStack.privateQueueContext]; NSTimeInterval today = [[NSDate date] timeIntervalSince1970]; aPerson.firstName = [NSString stringWithFormat:@"remark: %@", [NSString stringWithFormat:@"%f", today]]; aPerson.lastName = [NSString stringWithFormat:@"%f", today]; //NSLog(@"added %@ %@", aPerson.firstName, aPerson.lastName); [coreDataStack privateQueueContextOneSave]; } }]; return TRUE; } |
reportOnAllPeopleToLog
…simply reads the Core Data and displays the results.
Keep in mind that in the demo, the mainQueueContext is initialized with
NSMainQueueConcurrencyType, which runs on the main thread.
Thus, you SHOULD put tasks that update UI here.
If you are to use a time consuming task like a loop that displays all the People (like how we’re doing it), expect the main UI to freeze.
If you want to change it so that logging is done in the background and not freeze the UI, then change the NSMainQueueConcurrencyType to NSPrivateQueueConcurrencyType.
Then, put your code inside [self.mainQueueContext performBlock:^{…}.
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 |
-(void)reportOnAllPeopleToLog { NSLog(@"%s - reportOnAllPeopleToLog method called", __FUNCTION__); //Get all the projects in the data store NSFetchRequest * request = [[NSFetchRequest alloc] init]; NSEntityDescription * entity = [NSEntityDescription entityForName:@"Person" inManagedObjectContext:[self mainQueueContext]]; request.entity = entity; //use performBlock if your self.mainQueueContext is a Private Concurrent Queue [self.mainQueueContext performBlock:^{ //use it like this if your mainQueueContext is of Private MAIN Queue, which updates the UI NSArray *listOfPeople = [[self mainQueueContext] executeFetchRequest:request error:nil]; //List out contents of each project if( [listOfPeople count] == 0 ) { NSLog(@"%s - There are no people in the data store yet", __FUNCTION__); } else { NSLog(@"------------ RESULTS FROM DATABASE ------------"); NSLog(@"There are %lu number of entries so far", (unsigned long)[listOfPeople count]); [listOfPeople enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) { NSLog(@"%lu --- firstName = %@, lastName = %@", (unsigned long)idx, [obj firstName], [obj lastName]); //NSLog(@"object address is: %p", obj); }]; } }]; } |
Below are just some of the standard methods for the core stack.
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 |
+(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 { // 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"]; ... ... return _persistentStoreCoordinator; } - (NSManagedObjectContext *)privateQueueContext { // Returns the managed object context for the application (which is already bound to the persistent store coordinator for the application.) if (_privateQueueContext != nil) { return _privateQueueContext; } NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator]; if (!coordinator) { return nil; } _privateQueueContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType]; [_privateQueueContext setPersistentStoreCoordinator:coordinator]; NSLog(@"PrivateQueueContext CREATED" ); return _privateQueueContext; } @end |
Now, run the app. Make sure there is no previous version installed because we want a clean database.
Click on the add bulk button so we let it add 10,000 people into the database. Then play around with the scrollbar, you’ll notice that its responsive. Hence, we’ve successfully used the context to queue up jobs and run them on a background queue. The main thread is then responsive.
If you want to display all the Persons you’ve added, click on the display all button. The display all button uses the mainQueueContext, which runs on the main UI. Hence, put all the code that updates the UI, into that queue.
For demonstration purposes, we are using NSPrivateQueueConcurrencyType for the mainQueueContext. Hence, anything you do on on that context SHOULD BE TO UPDATE THE UI.
If you do labor intensive job such as logging all the People you’ve added, the UI will freeze…as demonstrated in the updated demo project.
IF you want to do the logging in the background, you can change it to NSPrivateQueueConcurrencyType:
1 |
_mainQueueContext = [[NSManagedObjectContext alloc] initWithConcurrencyType: NSPrivateQueueConcurrencyType]; |
then place your logging task in the performBlock like so:
1 2 3 |
[self.mainQueueContext performBlock:^{ //place your task here }]; |
Then, your logging will be executed in the background and not block the UI.