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.
Related
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.
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.
}
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.
When I call saveWithBlock:completion: it actually saves the data but doesn't call the completion Block. Is there a bug, or am I using this method the wrong way?
I use MagicalRecord 2.2 from CocoaPods. I've tried to downgrade to 2.1 with no luck -- the problem remains.
[MagicalRecord setupCoreDataStackWithInMemoryStore];
[MagicalRecord setDefaultModelNamed:#"Model.momd"];
[MagicalRecord saveWithBlock:^(NSManagedObjectContext *localContext) {
TestUser *test = [TestUser MR_createInContext:localContext];
test.name = #"Lweek";
} completion:^(BOOL success, NSError *error) {
NSLog(#"Yeah");
}];
This outputs nothing. The completion Block is not triggered, although the data are saved properly.
I am trying my hand at some very basic implementation of MagicalRecord to get the hang of it and run into the following.
When I save an entry and then fetch entries of that type it will come up with the entry I just saved. However, when I save the entry, close the app, start it again, and then fetch, it comes up empty.
Code for saving:
- (void)createTestTask{
NSManagedObjectContext *localContext = [NSManagedObjectContext contextForCurrentThread];
Task *task = [Task createInContext:localContext];
task.tName = #"First Task";
task.tDescription = #"First Task created with MagicalRecord. Huzzah!";
NSError *error;
[localContext save:&error];
if (error != Nil) {
NSLog(#"%#", error.description);
}
}
Code for fetching: (all I want to know here if anything is actually saved)
- (void) fetchTasks{
NSArray *tasks = [Task findAll];
NSLog(#"Found %d tasks", [tasks count]);
}
I am sure I am missing something here, but not anything I can seem to find on stackoverflow or in the Tutorials I looked at.
Any help is welcome.
I have to ask the obvious "Is it plugged in" question: Did you initialize the Core Data Stack with one of the +[MagicalRecord setupCoreDataStack] methods?
Did your stack initialize properly? That is, is your store and model compatible? When they aren't, MagicalRecord (more appropriately, Core Data) will set up the whole stack without the Persistent Store. This is annoying because it looks like everything is fine until it cannot save to the store...because there is no store. MagicalRecord has a +[MagicalRecord currentStack] method that you can use to examine the current state of the stack. Try that in the debugger after you've set up your stack.
Assuming you did that, the other thing to check is the error log. If you use
[localContext MR_saveToPersistentStoreAndWait];
Any errors should be logged to the console. Generally when you don't see data on a subsequent run of your app, it's because data was not saved when you thought you called save. And the save, in turn, does not happen because your data did not validate correctly. A common example is if you have a required property, and it's still nil at the time you call save. "Normal" core data does not log these problems at all, so you might think it worked, when, in fact, the save operation failed. MagicalRecord, on the other hand, will capture all those errors and log them to the console at least telling you what's going on with your data.
When i have started with magical record I was also facing this problem, problem is context which you are using to save data. here is my code which might help you
[MagicalRecord saveWithBlock:^(NSManagedObjectContext *localContext) {
NSArray *userInfoArray = [UserBasicInfo findByAttribute:#"userId" withValue:[NSNumber numberWithInt:loggedInUserId] inContext:localContext];
UserBasicInfo* userInfo;
if ([userInfoArray count]) {
userInfo = [userInfoArray objectAtIndex:0];
} else {
userInfo = [UserBasicInfo createInContext:localContext];
}
userInfo.activeUser = [NSNumber numberWithBool:YES];
userInfo.firstName = self.graphUser[#"first_name"];
userInfo.lastName = self.graphUser[#"last_name"];
userInfo.userId = #([jsonObject[#"UserId"] intValue]);
userInfo.networkUserId = #([jsonObject[#"NetworkUserId"] longLongValue]);
userInfo.userPoint = #([jsonObject[#"PointsEarned"] floatValue]);
userInfo.imageUrl = jsonObject[#"Picturelist"][0][#"PictureUrL"];
userInfo.imageUrlArray = [NSKeyedArchiver archivedDataWithRootObject:jsonObject[#"Picturelist"]];
} completion:^(BOOL success, NSError *error) {
}];
Use this when your done
[[NSManagedObjectContext MR_defaultContext]saveToPersistentStoreAndWait];