Reputation: 3706
I tried to find an answer to this question, but I couldn't figure out a question from neither the doc nor StackOverflow. If there is already a question like this, I just didn't find it, so it will be very welcomed as the solution in case.
My situation is: I have two core data entities, a User and a Driving Licence.
User <--- 1 to 1 ---> Driving Licence
I'm using Magical Record as an abstraction layer for the core data operations.
My User class (derived from NSManagedObject) exposes 2 methods.
One to access a singleton instance of the User (the only one used throughout the app):
+ (User *)currentUser {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
if ([User MR_findFirst] == nil) {
User *user = [User MR_createEntity];
user.drivingLicence = [DrivingLicence MR_createEntity];
[[user managedObjectContext] MR_save];
}
});
return [User MR_findFirst];
}
And a method used to reset the user data (derived from NSManagedObject):
- (void)resetFields
{
self.name = nil;
self.surname = nil;
....
[self.drivingLicence MR_deleteEntity];
self.drivingLicence = [DrivingLicence MR_createEntity];
[self.managedObjectContext MR_save];
}
Sometimes, I would say quite randomly, the drivingLicence field happens to be null. There may be occasions when the resetFields method is called by a background thread.
Could it be that, for the merge with the other contexts, tha sequence of instructions
[self.drivingLicence MR_deleteEntity];
self.drivingLicence = [DrivingLicence MR_createEntity];
can cause some confusion, bringing the drivingLicence to be just deleted at end? Or what else could it be the reason for this unexpected null value?
Upvotes: 2
Views: 603
Reputation: 1178
Just hit this problem. Based on our discoveries, I wanted to add some comments to casademora's answer above:
It is important to remember that core data, as well as any of the MR_save
methods, are not thread safe. We delegate our MagicalRecord actions to a queue to get around this issue. Specifically, it's important to remember not to do save actions (such as MR_saveToPersistentStoreAndWait
) in multiple threads at the same time.
However the connection between MR_createEntity
, MR_defaultContext
, and the Main thread is more subtle. As of MagicalRecord version 2.3.x, I do not believe that MR_createEntity
, with no arguments, defaults to MR_defaultContext
. I believe it defaults to the MR_contextForCurrentThread
. MR_contextForCurrentThread
returns the default context if it's on the Main thread, but otherwise it does not. This is dangerous if you don't understand the consequences, as you could easily see what the original poster sees above (lost data).
In fact, this note indicates that you should use MR_createEntityInContext:localContext
rather than MR_createEntity
due to problems with MR_contextForCurrentThread
when using GCD
.
See the function definition in github for the logic where MR_createEntity
uses MR_contextForCurrentThread
.
Upvotes: 0
Reputation: 69707
When you use MR_createEntity, you are implicitly using the default context, accessed through [NSManagedObjectContext MR_defaultContext]. It is quite dangerous to do this unless you are ABSOLUTELY POSITIVE you are calling that from the main thread. In your examples, all this should work correctly if everything is called from the main thread AND your self.managedObjectContext instance variable is also pointing to the default context. Otherwise, you will need to be explicit about which contexts you are using. MagicalRecord provides these conventions for you by having an inContext: optional parameter at the end of every method that requires a context to work. Have a look at the MR_createInContext: method and be explicit with your context usage
Upvotes: 2