0
votes

I've recently begun experimenting with Core Data's newer initWithConcurrencyType:NSPrivateQueueConcurrencyType construction of a Managed Object Context. When using this type of MOC, we should use performBlock: or performBlockAndWait: to "ensure the block operations are executed on the queue specified for the context."

As part of this I moved the Managed Object creation calls of initWithEntity:insertIntoManagedObjectContext: inside the performBlock: block, to be run on the MOC's private queue.

This matches the strategy shown in Apple's Core Data Concurrency article:

[private performBlock:^{
    for (NSDictionary *jsonObject in jsonArray) {
        NSManagedObject *mo = ... ; //Managed object that matches the incoming JSON structure
    }
    NSError *error = nil;
    if (![private save:&error]) {
        NSLog(@"Error saving context: %@\n%@", [error localizedDescription], [error userInfo]);
        abort();
    }
}];

I've found myself initializing managed objects in void methods that actually return the newly created object in a completion block.

+ (void)managedObjectFromJSON:(NSDictionary *)json completion:(void (^)(XYZManagedObject *object))completion;

This architecture introduces complexity compared to simply returning the objects from the JSON parsing method. I also seem to be hitting concurrency issues/crashes related to this design, perhaps in the async load of relationship Managed Objects chained together in these completion block constructors.

Each of my Managed Objects doing something like the below, returning the created object via a completion block called from within the performBlock: block.

[managedObjectContextPrivateQueue performBlock:^{
    NSEntityDescription *entity = [NSEntityDescription entityForName:@"entity" inManagedObjectContext:managedObjectContextPrivateQueue];
    XYZManagedObject *managedObject = [[XYZManagedObject alloc] initWithEntity:entity insertIntoManagedObjectContext:managedObjectContextPrivateQueue];

    [managedObjectContextPrivateQueue save:&error]

    completion(managedObject);
}];

Or, can the Managed Object be created outside the performBlock: block, and only insert/save operations moved inside? Note that the NSEntityDescription method does need to access the MOC outside the block.

NSEntityDescription *entity = [NSEntityDescription entityForName:@"entity" inManagedObjectContext:managedObjectContextPrivateQueue];
XYZManagedObject *managedObject = [[XYZManagedObject alloc] initWithEntity:entity insertIntoManagedObjectContext:nil];

[managedObjectContextPrivateQueue performBlock:^{
    [managedObjectContextPrivateQueue insertObject:managedObject];
    [managedObjectContextPrivateQueue save:&error]
}];

return managedObject;

Or, perhaps I should use performBlockAndWait: to wait for the block to run, then return the Managed Object from the method directly rather than from a completion block.

How should Managed Objects be created and returned when Managed Object Context call should be routed through the performBlock: private queue?

1
Why it is not possible to put a 'managedObjectFromJSON:` message into the block? - Amin Negm-Awad
The only initializers I found for NSManagedObject require a context - and DO INSERT the object into its context. If I understand it right, this insertion must occur within the PerformBlock: or PerformBlockAndWait: I hope I'm wrong here, but... - Motti Shneor

1 Answers

1
votes

You can create managed objects outside of a MOC. See -[NSManagedObjectContext insertObject:].

However, you probably don't want to do this in general. It's useful if you want to create an object that is conditional. For example, if you have a managed object that represents something the user might edit, you can create a non-managed version for editing, and only insert into the MOC when the user saves changes. It's not the best way, but it works.

However, your question is really about asynchronous operations with MOCs. My opinion is that the best thing to do is take a look at every API that deals with managed objects and figure out how to make them properly asynchronous. You should definitely not return a managed object from a method, unless you know the caller is running inside a performBlock{AndWait}: of the same MOC. This is because managed objects cannot be (reliably) accessed from outside the MOC that created them.

You can return objectIDs, and have the caller fetch the associated objects. It's often more tedious and less efficient, but it is the only reliable solution when crossing MOC boundaries.