Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Magical Record create/fetch entity in separate thread

I have a bunch of NSOperations that need to create and fetch entities while running in a queue.

  1. When creating entities in the NSOperations can I simply create using the default context, or since I am in a new thread not on the main thread is this not allowed/advised? If I create a new entity on this thread what is the best way to merge it back to the default context then (and not save to the store)?

  2. When fetching on these threads I need to fetch then update the data in that entity. I am at a loss as to how to do this and then merge to the default context.

Or does it really not matter what thread you fetch/create just when you save?

I say the default context above because I have NSFetchedResultsControllers monitoring the default context for updates and inserts. Currently my solution is working doing all of this on the main thread, but the app is starting to crawl with amount of data being run through it now.

Also I have a class monitoring (via KVO) the operation queue count. Once it gets to zero I preform a save. I would prefer to only save after all the above NSOperations are done executing like I am currently doing. Thank you for the help.

like image 968
inks2002 Avatar asked Feb 16 '23 13:02

inks2002


1 Answers

Let's ignore MagicalRecord for now. To work with Core Data on multiple threads, you need to be aware of a few things.

  1. Never pass an NSManagedObject between threads. Instead, pass around the NSManagedObjectID for the object you want, and re-fetch it in your background thread.
  2. Responsibly build your NSManagedObjectContext. This means you have to be aware of what initWithConcurrencyType: means. We will get into that.

Main Thread

Your main NSManagedObjectContext should be built with a concurrency type of NSMainQueueConcurrencyType. This will allow you to take advantage of the contexts built in queue for performing serial operations. Any time a background thread interacts with the main context, you should do the work using performBlock or performBlockAndWait.

- (NSManagedObjectContext *)managedObjectContext 
{
    if (_managedObjectContext != nil) {
        return _managedObjectContext;
    }

    NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator];
    if (coordinator != nil) {
        _managedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
        [_managedObjectContext setPersistentStoreCoordinator:coordinator];
    }

    return _managedObjectContext;
}

Background Thread

Whenever you are doing work in a background thread, you need to spin up a new context. You should not share contexts between threads. Pass in a reference to your main thread context into your operation and build a background context once your operation starts. This will ensure it is built on the thread where you will perform the work.

- (NSManagedObjectContext *)newBackgroundManagedObjectContext
{    
    // Create new context with private concurrency type
    NSManagedObjectContext *newContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
    [newContext setParentContext:self.mainContext];

    // Optimization
    [newContext setUndoManager:nil];

    return newContext;
}

Think of this background context as a scratch pad. Whatever you do there will stay there until you save. Because you set the parentContext, a save on your background context will merge changes onto your main context. This will update the NSFetchedResultsController, but the data has not yet been persisted as you haven't called save. In your queue KVO, you can call save on the main context by queuing up a save block.

[self performBlock:^{
    NSError *error;
    [self save:&error];
    if (error) {
        // handle errors
    }
}];
like image 156
Drewsmits Avatar answered Mar 04 '23 05:03

Drewsmits