Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

CoreData + MagicalRecord - Am I saving the details correctly?

I have three Applications in my database. I have a predicate that deletes one.

Given the following code:

[Application MR_deleteAllMatchingPredicate: applicationDeletePredicate];
[[NSManagedObjectContext MR_defaultContext] MR_saveToPersistentStoreAndWait];

NSLog(@"We have %ld apps left.", [Application MR_countOfEntities]);

dispatch_async(dispatch_get_main_queue(), ^{
    NSLog(@"We have %ld apps left.", [Application MR_countOfEntities]);
});

I get the following log messages:

We have 2 apps left.

We have 3 apps left.

So it has properly deleted the predicate, but then when I try to do reloading in the main queue, the application still exists. Am I doing this wrong? Should I always specify default context when dealing with MagicalRecord (in my count method)?

like image 204
Kyle Avatar asked Dec 24 '13 17:12

Kyle


1 Answers

Even though MagicalRecord does provide methods to reduce typing, you need to understand what’s happening behind the scenes to avoid situations like that.

In particular, you need to understand when MagicalRecord creates new contexts and how they are configured.

MagicalRecord provides methods for actions that require context, but those methods don’t take context as a parameter. In those cases MagicalRecord uses [NSManagedObjectContext MR_contextForCurrentThread] method to get the default context or create a new context that is a child of the default context. If it creates a new context, it caches it for the current thread for later reuse (which is currently a bad practice considering Apple’s direction towards contexts with queue concurrency types where you work with objects from within blocks).

But long story short, here’s what might be happening here.

  1. The code you provided runs on background thread.
  2. [Application MR_deleteAllMatchingPredicate: applicationDeletePredicate]; creates a new context that is child of the default context and performs the deletion.
  3. [[NSManagedObjectContext MR_defaultContext] MR_saveToPersistentStoreAndWait]; (which is illegally called not from the main thread considering 1., by the way) saves the default context that doesn’t know about the deletion, because child context was never saved and changes were not pushed to the parent, default, context.
  4. [Application MR_countOfEntities] in the first NSLog operates on the same context as the deletion and returns the new count.
  5. [Application MR_countOfEntities] in the second NSLog is run on the main queue. Because it is operating on the main thread, it uses default context, which never saw this deletion, because child never saved. So it returns the old count.

What you need to do here is to make sure the child context and all its parents are saved. So you probably need to replace the default context in the second line with the context for current thread: [[NSManagedObjectContext MR_contextForCurrentThread] MR_saveToPersistentStoreAndWait];.

But I would avoid hidden creation of context and would create them explicitly. So my first recommendation is to avoid using MagicalRecord methods that don’t take context as a parameter (when there are similar methods that do it).

My second recommendation is to not use MagicalRecord, but use Core Data directly. For the beginning you could have two contexts used globally: one with main queue concurrency type that you would use on the main thread to update the UI. And another one with private queue concurrency type that you would use for all background tasks, and that is configured as a child of the main context.

like image 181
eofster Avatar answered Nov 02 '22 19:11

eofster