I assume that you are developing for OS X / macOS ( NSTreeController
and NSOutlineView
). I have no experience with macOS - I'm developing for iOS - so you may need to consider this when you read my answer.
I haven't made the switch to fast yet - my code is probably Objective-C ...
I'll start by preparing the Core Data stack.
I set two general properties in the header file:
@property (nonatomic, strong) NSManagedObjectContext *mocPrivate; @property (nonatomic, strong) NSManagedObjectContext *mocMain;
Although this is optional, I also prefer to set private properties for Core Data objects, including, for example:
@property (nonatomic, strong) NSPersistentStoreCoordinator *persistentStoreCoordinator;
Once I pointed to my model URL, installed the NSManagedObjectModel
managed object model, pointed to my store URL for my NSPersistentStore
and set my NSPersistentStoreCoordinator
(PSC) persistent storage coordinator, I set up my two managed object contexts (MOCs).
As part of the “build” method of my base data stack, after I completed the code in the paragraph above, I include the following ...
if (!self.mocPrivate) { self.mocPrivate = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType]; [self.mocPrivate setPersistentStoreCoordinator:self.persistentStoreCoordinator]; } else { // report to console the use of existing MOC } if (!self.mocMain) { self.mocMain = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType]; [self.mocMain setParentContext:self.mocPrivate]; } else { // report to console the use of existing MOC }
(I usually include a few NSLog
lines in this code to report my console, but I excluded it here to keep the code clean.)
Pay attention to two important aspects of this code ...
- establish a private queue MOC to interact with the PSC; and
- set the primary MOC queue as a child of the private queue MOC queue.
Why is this done? First, highlight a couple of important points:
- Saving memory is relatively fast; and
- Saving to disk is relatively slow.
The private queue is asynchronous to the main queue. The user interface (UI) runs in the main queue. The private queue runs in a separate thread in the background, which works to maintain context and coordinate data storage with PSC, perfectly managed by Core Data and iOS. The main queue works with the main thread with the user interface.
Written in another way ...
- Hard work that completes the irregular (basic data-driven) data constant for the PSC (saves to disk) ends in a private queue; and
- In the main queue, work is completed to complete the regular (managed by the developer) data storage in the MOC (stored in memory).
In theory, this should ensure that your user interface is never blocked.
But there is more to it. How we manage the save process is important ...
I am writing a public method:
- (void)saveContextAndWait:(BOOL)wait;
I call this method from any class that needs to save data. The code for this public method:
- (void)saveContextAndWait:(BOOL)wait { // 1. First if ([self.mocMain hasChanges]) { // 2. Second [self.mocMain performBlockAndWait:^{ NSError __autoreleasing *error; BOOL success; if (!(success = [self.mocMain save:&error])) { // error handling } else { // report success to the console } }]; } else { NSLog(@"%@ - %@ - CORE DATA - reports no changes to managedObjectContext MAIN_", NSStringFromClass(self.class), NSStringFromSelector(_cmd)); } // 3. Third void (^savePrivate) (void) = ^{ NSError __autoreleasing *error; BOOL success; if (!(success = [self.mocPrivate save:&error])) { // error handling } else { // report success to the console } }; // 4. Fourth if ([self.mocPrivate hasChanges]) { // 5. Fifth if (wait) { [self.mocPrivate performBlockAndWait:savePrivate]; } else { [self.mocPrivate performBlock:savePrivate]; } } else { NSLog(@"%@ - %@ - CORE DATA - reports no changes to managedObjectContext PRIVATE_", NSStringFromClass(self.class), NSStringFromSelector(_cmd)); } }
So, I will work through this to explain what is happening.
I offer the developer the ability to save and wait (block), and depending on the developer’s use of the saveContextAndWait:wait
method, the saveContextAndWait:wait
private queue “saves” using either:
performBlockAndWait
method (method for invoking a developer with wait
= TRUE
or YES
); orperformBlock
method (a method for invoking a developer with wait
= FALSE
or NO
).
The method first checks to see if there are any changes in the main MOC queue. Do not do any work if we do not need!
Secondly, the method completes the (synchronous) call to performBlockAndWait
in the main MOC queue. This makes a call to the save
method in the code block and waits for completion before allowing the code to continue. Remember that this is the usual “saving" of small data sets. The option (asynchronous) for calling performBlock
is not required here and will actually lead to a disruption in the effectiveness of the method, as I experienced when I learned to implement this in my code (it was not possible to save data due to the save
call in the main MOC queue trying to execute after terminating save
in the private MOC queue).
Thirdly, we write a small block in a block that contains code to save a private MOC queue.
Fourth, the method checks to see if there are any changes in the MOC of the private queue. This may not be necessary, but it is harmless to include here.
Fifth, depending on the option that the developer wants to implement ( wait
= YES
or NO
), the method calls either performBlockAndWait
or performBlock
on the block inside the block (under the third above).
At this last stage, regardless of the implementation ( wait
= YES
or NO
), the function of saving data to disk from the private MOC queue in the PSC is abstracted in the private queue on the asynchronous stream into the main stream. Theoretically, “saving to disk” via PSC can take as much time as he likes, because it has nothing to do with the main stream. And since the private MOC queue has all the data in memory, the main MOC queue is fully and automatically informed of the changes, since it is a child node of the MOC of the private queue.
If you are importing large amounts of data into the application, then I am working on an implementation, then it makes sense to import this data into the private MOC queue.
The MOC private queue does two things here:
- It coordinates data storage (to disk) using PSC;
- Since he is the parent of the MOC main queue (in memory), the MOC of the main queue will be notified of data changes in the MOC of the private queue, and the merges are managed by Core Data;
Finally, I use the NSFetchedResultsController
(FRC) to manage my data retrievals, which are all completed in relation to the main MOC queue. This maintains a data hierarchy. Because changes are made to the dataset in any context, FRC updates the view.
This is a simple solution! (Once I spent weeks stumbling about it, and a few weeks updated my code.)
There is no need to track notifications of mergers or other changes in the MOC. Core Data and iOS process everything in the background.
So, if this does not work for you - let me know - I may have ruled out or missed something since I wrote this code a year ago.