Hello guys I am developing an app which uses coredata (multithreaded), below is the coredata stack used (this was designed via a tutorial found here: https://www.cocoanetics.com/2012/07/multi-context-coredata/)
THE MODEL
- (NSManagedObjectModel *)managedObjectModel {
if (_managedObjectModel != nil) {
return _managedObjectModel;
}
NSURL *modelURL = [[NSBundle mainBundle] URLForResource:@"XXX" withExtension:@"momd"];
_managedObjectModel = [[NSManagedObjectModel alloc] initWithContentsOfURL:modelURL];
return _managedObjectModel;
}
THE MAIN CONTEXT
- (NSManagedObjectContext *)managedObjectContext{
if (_managedObjectContext != nil) {
return _managedObjectContext;
}
_managedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
_managedObjectContext.parentContext = [self writerManagedObjectContext];
return _managedObjectContext;
}
THE WRITER CONTEXT
- (NSManagedObjectContext *)writerManagedObjectContext{
if (_writerManagedObjectContext != nil) {
return _writerManagedObjectContext;
}
NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator];
if (coordinator != nil) {
_writerManagedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
[_writerManagedObjectContext setPersistentStoreCoordinator:coordinator];
}
return _writerManagedObjectContext;
}
THE PERSISTENT STORE COORD
- (NSPersistentStoreCoordinator *)persistentStoreCoordinator {
if (_persistentStoreCoordinator != nil) {
return _persistentStoreCoordinator;
}
_persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:[self managedObjectModel]];
NSURL *storeURL = [[self applicationDocumentsDirectory] URLByAppendingPathComponent:@"XXX.sqlite"];
NSError *error = nil;
NSString *failureReason = @"There was an error creating or loading the application's saved data.";
if (![_persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:@"XXX" URL:storeURL options:options error:&error]) {
NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
abort();
}
}
As you can see, the WRITER context is the parent of the MAIN context which means that the WRITER would handle saving data to the store whilst it also updates the MAIN context (in memory) of any changes.
NOTE: the WRITER save to the store because the POS was set to the WRITER context.
On the entities in the model, I set 'Id' as a unique constraint for all entities (tables) in the database which is used for UPSERT (i.e. an equivalent of SQL insert OR replace
).
And both the MAIN and WRITER contexts have their merge policies set to NSMergeByPropertyObjectTrumpMergePolicy
which ensures objects between the WRITER, the MAIN and the store are in sync.
[[CoreDataCommons mainContext] setMergePolicy:NSMergeByPropertyObjectTrumpMergePolicy];
[[CoreDataCommons writerContext] setMergePolicy:NSMergeByPropertyObjectTrumpMergePolicy];
Now when I want to insert to the database I create a new context:
NSManagedObjectContext *context = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
[context setParentContext:writerManagedObjectContext];
[context performBlock:^{
// Populate entity attributes and save
for (id object in objects){
// Save every 500 objects
if (count % 500 == 0){
if ([context save:&error]){
}
}
count++;
}
// Final save if anything unsaved
if ([context save:&error]){
}
// Save WRITER context which will push changes to MAIN context
[writerManagedObjectContext performBlock:^{
if (![writerManagedObjectContext save:&writerError]) {
DLog(@"Writer: Unresolved error %@, %@", writerError, [writerError localizedDescription]);
}];
}];
THE PROBLEM
When the app is loaded for the very first time, it loads about 15000 objects (type JSON) from API and it writes this data in about 3 seconds (which I think is a bit too long BUT NOT THE MAIN ISSUE); however the first time load is not the issues. The ISSUE arises from subsequent loads from API; so, the second time it loads it takes about 5 mins to write the same data and it also BLOCKS THE MAIN THREAD.
After a few debugging I found that the constraint (i.e. the UPSERT) causes it to take too long to save when there is data already in the database. DOES THIS MEAN THAT ITS ALSO DOING THE PRIMITIVE
IF (EXIST){ // UPDATE}ELSE{ // INSERT}
I have used multiple context to ensure this is not the case but it still seems to hold the main thread and takes a ridiculous amount to time to save.
Questions Firstly, could there be any issue caused by the coredata stack (i.e. a deadlock,background process etc)?
Secondly: Is this time taken to save the object normal with coredata? if not could anyone suggest an optimisation strategy.
Thirdly I am considering bypassing coredata and using sqlite directly. Any foreseen obstacles with this? apart from security layer provided by coredata.
Any ideas are welcome.
Thanks in advance.