1

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.

  • `save` only once at the end of `performBlock`, not for every object. – shallowThought Feb 06 '17 at 12:43
  • I am not saving every object, I save every 500/1000 objects entered, this reduces the overhead time taken if all objects were to be saved at once in the end. – user3355301 Feb 06 '17 at 13:37
  • Have you profiled your app in Instruments? There is a Core Data instrument which can be very helpful in showing you what your app is spending all its time doing. Core Data doesn't by default do upsert operations, you would need to handle that yourself. – Dave Weston Feb 06 '17 at 21:53
  • I will try the coredata instruments to see what I can find. Also coredata (from ios9) supports upsert by allowing constraint in the model and setting the trump policy to ensure objects are in sync. – user3355301 Feb 07 '17 at 09:24

0 Answers0