Nil for MR_inContext - ios

according to this tutorial (https://github.com/magicalpanda/MagicalRecord/blob/master/Docs/Working-with-Managed-Object-Contexts.md) I tried to find my device and update it.
Person *person = ...;
[MagicalRecord saveWithBlock:^(NSManagedObjectContext *localContext){
Person *localPerson = [person MR_inContext:localContext];
localPerson.firstName = #"John";
localPerson.lastName = #"Appleseed";
} completion:^(BOOL success, NSError *error) {
self.everyoneInTheDepartment = [Person findAll];
}];
So I made:
CDDevice *device = [CDDevice MR_findFirstByAttribute:#"deviceName"
withValue:uniqueName];
Which found my device. After few IF statements where i test if device have proper session and authorization code I want to update it.
[MagicalRecord saveWithBlock:^(NSManagedObjectContext *localContext){
CDDevice * localDevice = [device MR_inContext:localContext];
[localDevice updateFromDictionary:messageDictionary];
} completion:^(BOOL success, NSError *error) {
NET_LOG(#"Updating current device %#", device);
}];
But all the time my localDevice is nil. Is it because MR_findFirstByAttribute running in different context? What is correct way to update my device?
All of this happing on my custom serial queue, because this code is in network part of project. (Receviver method with GCDAsyncUdpSocket )

Make sure you save your data before retrieving it in the background context.

Related

No known class method for selector 'MR_inContext:'

Please help. I'm very new about ios app development. I'm trying to use MagicalRecord to save data in objective-C. However, I already install the MagicalRecord, but MR_inContext still not working.
Person *person = 'what should goes here?';
[MagicalRecord saveWithBlock:^(NSManagedObjectContext *localContext){
Person *localPerson = [person MR_inContext:localContext];
localPerson.firstName = #"John";
} completion:^(BOOL success, NSError *error) {}
I am trying to do something like MagicalRecord document.

How do you access NSManagedObjects between blocks?

Like the title says how does one go about accessing an NSManagedObject that has been created in one block and then needs to be accessed in the other. I have the following implementation and was wondering if it's correct.
__block Person *newPerson;
#weakify(self);
[MagicalRecord saveWithBlock:^(NSManagedObjectContext *localContext) {
newPerson = [Person MR_createInContext:localContext];
newPerson.name = #"Bob";
} completion:^(BOOL success, NSError *error) {
#strongify(self);
// Called on main thread
PersonViewController *personVC = [[PersonViewController alloc] initWithPerson:newPerson];
[self.navigationController pushViewController:personVC animated:YES];
}];
Am I correct in not needing to access newPerson from a localContext in the completion handler because it'll be executed on the main thread?
EDIT
It looks like the following is the proposed way:
__block NSManagedObjectID *newPersonObjectID;
#weakify(self);
[MagicalRecord saveWithBlock:^(NSManagedObjectContext *localContext) {
Person *newPerson = [Person MR_createInContext:localContext];
newPerson.name = #"Bob";
newPersonObjectID = newPerson.objectID;
} completion:^(BOOL success, NSError *error) {
#strongify(self);
// Called on main thread
Person *savedPerson = [[NSManagedObjectContext MR_defaultContext] objectWithID:newPersonObjectID];
PersonViewController *personVC = [[PersonViewController alloc] initWithPerson:savedPerson];
[self.navigationController pushViewController:personVC animated:YES];
}];
Solution
This answer and comments lead to the following solution.
A TemporaryID is being assigned to the object whilst it's being saved and therefore when trying to fetch the object with the TempID an exception occurs.
Rather than creating a whole new fetch request what can be done is asking the context to obtain the permanent IDs early and than acquiring the permanent ID of the object. For example:
__block NSManagedObjectID *newPersonObjectID;
#weakify(self);
[MagicalRecord saveWithBlock:^(NSManagedObjectContext *localContext) {
Person *newPerson = [Person MR_createInContext:localContext];
newPerson.name = #"Bob";
[localContext obtainPermanentIDsForObjects:#[newPerson] error:NULL];
newPersonObjectID = newPerson.objectID;
} completion:^(BOOL success, NSError *error) {
#strongify(self);
// Called on main thread
Person *savedPerson = [[NSManagedObjectContext MR_defaultContext] objectWithID:newPersonObjectID];
PersonViewController *personVC = [[PersonViewController alloc] initWithPerson:savedPerson];
[self.navigationController pushViewController:personVC animated:YES];
}];
You can't directly pass managed objects between contexts. Each NSManagedObject can only be accessed by its own context.
You'll need to pass its objectID to the completion block, then have the main context fetch the object by calling one of the following methods:
-(NSManagedObject *)objectWithID:(NSManagedObjectID *)objectID
This will create a fault to an object with the specified objectID, whether or not it actually exists in the store. If it doesn't exist, anything that fires the fault will fail with an exception.
-(NSManagedObject *)existingObjectWithID:(NSManagedObjectID *)objectID
error:(NSError **)error
This will fetch the object from the store that has that ID, or return nil if it doesn't exist. Unlike objectWithID, the object won't be faulted; all its attributes will have been retrieved.
In either case, your local context must have saved the Person object to the store for the main context to be able to fetch it.
More details about objectID can be found in the Core Data Programming Guide
Edit by User Asking Question
This answer and comments lead to the correct solution.
A TemporaryID is being assigned to the object whilst it's being saved and therefore when trying to fetch the object with the TempID an exception occurs.
Rather than creating a whole new fetch request what can be done is asking the context to obtain the permanent IDs early and than acquiring the permanent ID of the object. For example:
__block NSManagedObjectID *newPersonObjectID;
#weakify(self);
[MagicalRecord saveWithBlock:^(NSManagedObjectContext *localContext) {
Person *newPerson = [Person MR_createInContext:localContext];
newPerson.name = #"Bob";
[localContext obtainPermanentIDsForObjects:#[newPerson] error:NULL];
newPersonObjectID = newPerson.objectID;
} completion:^(BOOL success, NSError *error) {
#strongify(self);
// Called on main thread
Person *savedPerson = [[NSManagedObjectContext MR_defaultContext] objectWithID:newPersonObjectID];
PersonViewController *personVC = [[PersonViewController alloc] initWithPerson:savedPerson];
[self.navigationController pushViewController:personVC animated:YES];
}];
You should access the objectID outside of your block (on the main thread, for example) and then use it within your block. Something like:
NSManagedObjectID *objectID = appDelegate.myObject.objectId;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
// use objectID here.
}

Doesn't MagicalRecord work in background thread?

It seems I tried everything but it seems it works in main thread only. For example:
[SomeClass MR_createEntity];
[[NSManagedObjectContext MR_defaultContext] MR_saveWithOptions:MRSaveSynchronously completion:^(BOOL success, NSError *error) {
if (success) {
NSLog(#"You successfully saved your context.");
} else if (error) {
NSLog(#"Error saving context: %#", error.description);
}
}];
If this code is run in main thread then success == YES otherwise (in background thread) it gives success == NO. In both cases error == nil.
So is it impossible to call the saving in background thread?
Completion blocks are always called from the main thread, here's an example that should work:
Person *person = ...;
[MagicalRecord saveWithBlock:^(NSManagedObjectContext *localContext){
Person *localPerson = [person MR_inContext:localContext];
localPerson.firstName = #"John";
localPerson.lastName = #"Appleseed";
} completion:^(BOOL success, NSError *error) {
self.everyoneInTheDepartment = [Person findAll];
}];
Reference: https://github.com/magicalpanda/MagicalRecord/blob/master/Docs/Working-with-Managed-Object-Contexts.md
Finally I hadn't to create a workable project with fully background MagicalRecord work.
The best solution for me is to update database in the main thread only and to read the database in any thread (including background). Additionally I show custom progress view on database updating.

SIGSEGV crash on MagicalRecord MR_contextWillSave:

I have an app in TestFlight that keeps crashing on the first core data save. I've never been able to recreate the issue myself, but It is happening multiple times to multiple users. Im getting a set of taxonomy from our server as JSON then saving the results to core data like so:
[MagicalRecord saveWithBlock:^(NSManagedObjectContext *localContext) {
[self createRestrictions:restrictions inContext:localContext];
} completion:^(BOOL success, NSError *error) {
if(success){
dispatch_async(dispatch_get_main_queue(), ^(void) {
DLog(#"got and saved all restrictions");
if(complete){
complete(YES);
}
});
}else{
TFLog(#"error saving restrictions: %#", error);
if(failure){
failure(#"Error saving restrictions");
}
}
}];
-(void)createRestrictions:(NSArray *)restrictions
inContext:(NSManagedObjectContext *)context
{
for(NSDictionary *_restriction in restrictions){
Restriction *restriction = [Restriction MR_findFirstByAttribute:#"tid"
withValue:[NSNumber numberWithInt:[_restriction[#"tid"] intValue]]
inContext:context];
if(!restriction){
restriction = [Restriction MR_createInContext:context];
restriction.tid = [NSNumber numberWithInt:[_restriction[#"tid"] intValue]];
}
...
}
}
Here is a screenshot of the crash log from TestFlight

Magical Record, how to use saveWithBlock and still access imported data afterwards

the idea is to import server JSON response into core data without blocking UI on main thread, I still need the imported entities afterwards, after a whole morning testing/googling, I failed to find the right way to do this.
__block NSMutableArray *cars;
[MagicalRecord saveWithBlock:^(NSManagedObjectContext *localContext) {
for (NSDictionary *carObject in carObjects) {
Notification *car = [Notification MR_importFromObject:carObject inContext:localContext];
[cars addObject:car];
}
} completion:^(BOOL success, NSError *error) {
if (success) {
for (Car *car in cars) {
// data may have invalid data or be nil
// [Car findAll] will have correct data though
}
}
}];
interestingly, when I use following code, it works. seems the importing is done in background thread as well!
I really don't know in which context the importing is done, but the UI blocking issue is gone.
__block NSMutableArray *cars;
[MagicalRecord saveWithBlock:^(NSManagedObjectContext *localContext) {
for (NSDictionary *carObject in carObjects) {
Notification *car = [Notification MR_importFromObject:carObject]; // not pass in localContext
[cars addObject:car];
}
} completion:^(BOOL success, NSError *error) {
if (success) {
for (Car *car in cars) {
// data may have invalid data or be nil
// [Car findAll] will have correct data though
}
}
}];

Resources