Restkit Core data integration with NSManagedObjectContext - iphone

For the last weeks I am learning Restkit (v0.10.0) and core data and the possibilities are endless with these great tools. Problem is I am a bit overwhelmed on how to see the bigger picture here. And because of the very fast paced updating of Restkit most of the tutorials/demo code is out of date and not working properly any more.
I have managed to get my tableview filled with data from my json on a remote server. I also worked out on how to make the remote data leading in combination with caching working now, but I am struggling with the NSManagedObjectContext/NSEntityDescription (Core data) and how it works out with Restkit when using POST commands.
If I understand it correctly the record is created in Core Data (after the comment line // Create a new instance ) and after that that data is used to create a POST request so that the record is posted to the server.
This code is being used to create a new record on the server but when the code is executed (I see a record being created on my server) but my tableview is not updated accordingly, the table view is not updated and therefore the new record is first visible when restarting the app. Manually refreshing the data from the server does not help either.
Hopefully someone can give me some pointers, or maybe a tutorial with Restkit/core data and a POST combined. Thanks!
- (void)createGoalWithName:(NSString *)name andDescription:(NSString *)goalDescription
{
Goal* goal = [Goal object];
goal.identifier = 0;
goal.name = name;
goal.goalDescription = goalDescription;
// Create a new instance of the entity managed by the fetched results controller.
NSManagedObjectContext *context = [self.fetchedResultsController managedObjectContext];
NSEntityDescription *entity = [[self.fetchedResultsController fetchRequest] entity];
[NSEntityDescription insertNewObjectForEntityForName:[entity name] inManagedObjectContext:context];
[self saveContext];
[[RKObjectManager sharedManager].router routeClass:[Goal class] toResourcePath:#"/api/goals" forMethod:RKRequestMethodPOST];
[[RKObjectManager sharedManager] postObject:goal delegate:self];
[self.tableView reloadData];
}
- (void)saveContext {
NSManagedObjectContext *context = [self.fetchedResultsController managedObjectContext];
NSError *error = nil;
if (![context save:&error]) {
/*
Replace this implementation with code to handle the error appropriately.
abort() causes the application to generate a crash log and terminate.
You should not use this function in a shipping application,
although it may be useful during development.
If it is not possible to recover from the error,
display an alert panel that instructs the user to quit the application by pressing the Home button.
*/
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}

You have to use blocks when using RestKit+CoreData, and forget the router setup:
NSString *postUrl = #"/someurl/newelement";
[ [RKObjectManager sharedManager] loadObjectsAtResourcePath:postUrl usingBlock:^(RKObjectLoader* loader) {
loader.serializationMIMEType = RKMIMETypeJSON;
loader.delegate = nil;
loader.targetObject = nil;
loader.method= RKRequestMethodPOST; // change to GET, POST, PUT etc
}];

Since you don't include the UI code, it's hard to diagnose this problem fully, but one thing that might be happening since the updates are showing up when you restart the app, is that you're not properly synchronizing changes between the various thread's local managed object contexts. RestKit has its own managed object context since it doesn't run on the main UI thread.
The concept of working with multiple threads in Core Data is covered in this Apple document: http://developer.apple.com/library/ios/#documentation/cocoa/conceptual/CoreData/Articles/cdConcurrency.html but the gist of it is that you need to register for the notification NSManagedObjectContextDidSaveNotification and then invoke mergeChangesFromContextDidSaveNotification: on the UI thread's managed object context to safely merge the changes done on the RestKit thread.
Keep in mind that the notification will be posted on the RestKit thread, so you probably have to run the update on the main UI thread, e.g. something like this in the method receiving the notification:
[self.managedObjectContextForMainThread performSelectorOnMainThread:#selector(mergeChangesFromContextDidSaveNotification:) withObject:notification waitUntilDone:YES];
Where the property NSManagedObjectContext* managedObjectContextForMainThread has been properly initialized to point to the UI thread's managed object context.
Hope this helps (if you haven't abandoned RestKit altogether...)

I'm still using a slightly older version of Restkit. But one key element is that a primary key attribute must be defined.
So that Restkit can keep your local stored objects and server objects in sync.
In your case, when defining mappings for your Goal object, you would do it like so:
goalMapping.primaryKeyAttribute = #"identifier";

Related

Core data saving in thread

I'm developing a client server application in iPad. I need to save quite a number of data the server sends me. it sends me a long string, and i have to break it up into small records and save it in core data. it sends me a total of probably 20 messages and each messages has roughly 100 over records.
The problem now is the user has to wait for all the messages to be saved into the core data, before the UI unfreezes, as its all running in the main thread.
Question, Can i receive the message from server, and throw the breaking up of data and saving into core data into threads? i keep getting the sigbart error when the context is save. I checked the core data, it saves about 4 records before hitting that error.
Can multiple threads access / save into core data at the same time?
Sorry i'm really lost. tried the open source Magical Records but it keep having errors.
A Core Data managed object context is not thread safe. While you can have a single Core Data store, you need to create a separate managed object context for each thread. If you need to pass references to managed objects between threads, you need to pass the object ID and then read the object from the local managed object context, rather than trying to pass the object itself.
Doing this will enable you to save in the background using Core Data.
Beware when saving on background threads however that the app could exit before the background thread is finished saving. See this discussion.
Since Core Data requires one Managed Object Context per thread, a possible solution would be to track a context per Thread in a global manager, then track save notifications and propagate to all Threads:
Assuming:
#property (nonatomic, strong) NSDictionary* threadsDictionary;
Here is how to get the managed object (per thread):
- (NSManagedObjectContext *) managedObjectContextForThread {
// Per thread, give one back
NSString* threadName = [NSString stringWithFormat:#"%d",[NSThread currentThread].hash];
NSManagedObjectContext * existingContext = [self.threadsDictionary objectForKey:threadName];
if (existingContext==nil){
existingContext = [[NSManagedObjectContext alloc] init];
[existingContext setPersistentStoreCoordinator: [self persistentStoreCoordinator]];
[self.threadsDictionary setValue:existingContext forKey:threadName];
}
return existingContext;
}
At some point in the init method of your global manager (I used a singleton):
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(backgroundContextDidSave:) name:NSManagedObjectContextDidSaveNotification object:nil];
Then to receive save notifications and propagate to all other managed context objects:
- (void)backgroundContextDidSave:(NSNotification *)notification {
/* Make sure we're on the main thread when updating the main context */
if (![NSThread isMainThread]) {
[self performSelectorOnMainThread:#selector(backgroundContextDidSave:)
withObject:notification
waitUntilDone:NO];
return;
}
/* merge in the changes to the main context */
for (NSManagedObjectContext* context in [self.threadsDictionary allValues]){
[context mergeChangesFromContextDidSaveNotification:notification];
}
}
(some other methods were removed for clarity)

Core Data: Adding objects from NSMutableArray to NSMutableSet?

Judging from the amount of related questions that were brought up, this looks like a frequently asked question which makes me all the more hesitant to ask it. I have looked at a majority of these questions but none of them seem to address the specific problem I am having trouble figuring out.
A little background information on what I'm trying to achieve, how I am going about it, and what issue I am having:
I am trying to parse an XML web service, load that data into an NSMutableArray(Or any other place from which I can access it later), and then take my data and load it into my Core Data model. The first part of this I can do, its once I have my information in the array and trying to load it into Core Data that I cannot seem to progress.
My model(simplified for this question) consists of a route entity that has a one to many relationship with a checkpoint entity. The data I would be trying to load is a variety of attribute information into my route entity, which is not included in my array, and then my list of checkpoints, which is what the array is. My problem is that I cannot reliably add my entire array of checkpoints and then save. For the static case I am using for development, I have a consistent 20 checkpoints being parsed into my NSMutableArray, of these, the most I have been able to transfer into my NSMutableSet aka the checkpoints part of my route entity is 7 before crashing with either a SIGABRT, or EXC_BAD ACCESS, or incorrect selector sent. I have been trying to figure it out for the better part of today with no luck. Now for some code:
NSManagedObjectContext *context = [appDelegate managedObjectContext];
//newRoute is the route that I am trying to create and then store persistently
newRoute = [NSEntityDescription insertNewObjectForEntityForName:#"Route" inManagedObjectContext:context];
//filling in some available attribute information
if (name.text == #"")
[newRoute setValue:[NSDate date] forKey:#"name"];
else
[newRoute setValue:name.text forKey:#"name"];
NSMutableSet *muteSet = [newRoute mutableSetValueForKey:#"myCheckpoints"];
for (int i = 0; i < [appDelegate.checkpoints count]; i++ )
{
Checkpoint *newCheckpoint = [[Checkpoint alloc] init];
[newCheckpoint setName:[[appDelegate.checkpoints objectAtIndex:i] valueForKey:#"name"]];
NSLog(#"Appending %# to set", newCheckpoint.name);
[muteSet addObject:newCheckpoint];
[newCheckpoint release];
}
//myCheckpoints is the route<-->>checkpoints relationship
[newRoute setValue:muteSet forKey:#"myCheckpoints"];
// Save the context.
NSError *error = nil;
if (![context save:&error])
{
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}
Out of curiosity, why doesn't the following work?
NSSet *ckPtSet = [[NSSet alloc] initWithArray:appDelegate.checkpoints];
[newRoute setValue:ckPtSet forKey:#"myCheckpoints"];
As far as I understand, and this might be where the problem is... when setting the value of myCheckpoints, the expectation is to be passed an NSSet. When going through with the debugger the initialized set actually contains the 20 objects, but when I try to step past I get the incorrect selector received error again.
Anyways, thank you for taking the time to read my wall of text, if you need more to help please let me know and I will add it asap!
-Karoly
The documentation for [id<NSKeyValueCoding> mutableSetValueForKey:] states that it returns a mutable set proxy that provides read-write access to the unordered to-many relationship specified by a given key. This means that the object returned isn't necessarily an NSMutableSet instance per se, but a proxy wherein any changes you make to that object are reflected in your model's set itself.
This might be why [newRoute setValue:muteSet forKey:#"myCheckpoints"]; is giving you troubles. I find that a better way to think about it is to not have an intermediate object, but to nest calls, e.g.:
[[newRoute mutableSetValueForKey:#"myCheckpoints"] addObject:newCheckpoint];

NSFetchRequest cause SIGABRT OR EXC_BAD_ACCESS

I'm using this simple code for my fetch request
NSArray *fetchResults = [moc executeFetchRequest:request error:&error];
NSLog(#" i want show my result : %#",fetchResults); -> cause SIGABRT
If i'm using on my persistent store just after this creation, i have an error.
PS: the store was save between the populate and the request.
But if i close the app, and reopen ( in this case the store exist), i have no error.
in some case i can view this message : terminate called after throwing an instance of 'NSException'
but i can't access to this exception.
if i count the fetch results, i have a good number, it's really strange.
Thanks for help.
Okay, I have found the problem!
In the populate code, one of my relationships was insert with an autorelease.
Remove this, and now it's OK.
This is not a good solution:
NSManagedObject *relationEntity = [[NSEntityDescription insertNewObjectForEntityForName:#"picture" inManagedObjectContext:moc] autorelease];
Simply remove autorelease:
NSManagedObject *relationEntity = [NSEntityDescription insertNewObjectForEntityForName:#"picture" inManagedObjectContext:moc];
I have forgotten this in core data (don't use release, just set object to nil)!

could not locate an NSManagedObjectModel for entity name

This is the code for toggleAddProject method, the Core Data code is almost the same as found in Apple's CoreDataBooks sample, however when I click the add button the app crashes with entityForName: could not locate an NSManagedObjectModel for entity name 'Project' on the line starting with newProjectController.project
-(IBAction)toggleAddProject
{
NewProjectViewController *newProjectController = [[[NewProjectViewController alloc] initWithStyle:UITableViewStyleGrouped] autorelease];
// Create a new managed object context for the new project -- set its persistent store coordinator to the same as that from the fetched results controller's context.
NSManagedObjectContext *addingContext = [[NSManagedObjectContext alloc] init];
self.addingManagedObjectContext = addingContext;
[addingManagedObjectContext setPersistentStoreCoordinator:[[fetchedResultsController managedObjectContext] persistentStoreCoordinator]];
newProjectController.project = (Project *)[NSEntityDescription insertNewObjectForEntityForName:#"Project" inManagedObjectContext:addingContext];
[addingContext release];
UINavigationController *addNewNavigationController = [[UINavigationController alloc] initWithRootViewController:newProjectController];
[self.navigationController presentModalViewController:addNewNavigationController animated:YES];
[addNewNavigationController release];
}
Everything has been synthesized, the Project entity exists. I can't figure out why it crashes. Most people seem to be able to fix this error by inserting the following code either in the method itself, or in viewDidLoad:
if (managedObjectContext == nil)
{
managedObjectContext = [(CoreDataBooksAppDelegate *)[[UIApplication sharedApplication] delegate] managedObjectContext];
}
When modified for my app delegate it makes no difference. Thanks for any help.
This error has only a few possible sources:
Typo in the Entity name.
Nil managed object context object.
Failure to add the model containing the entity to the persistent store the context uses.
Failure to add the correct persistent store to the context itself.
I had this problem when I had several different NSManagedObjectContexts. The quick way to debug it was to inspect the different connection bits and make sure my entity was listed before calling the context.
NSLog(#"Context: %#",context);
NSLog(#"PS Coord : %#",context.persistentStoreCoordinator);
NSLog(#"MOM : %#", context.persistentStoreCoordinator.managedObjectModel);
NSLog(#"Entities : %#",[[context.persistentStoreCoordinator.managedObjectModel entities] valueForKey:#"name"]);
Use the debugger and confirm that your model is not nil. That is the most common cause of this error. If it is not nil then look for a typo in the entity name.
The Apple docs give some good information on debugging the error entityForName: could not locate an NSManagedObjectModel for entity name 'Foo'.
Look at this section of the Core Data Programming Guide.
Ok I ran across this issue as well and I solved it thusly. The original code was given as:
Event *event = (Event *)[NSEntityDescription insertNewObjectForEntityForName:#"Event" inManagedObjectContext:managedObjectContext];
While the code is concise it seems like the debugger can't display more detailed information about where the error is since you are both creating and configuring a new instance of the 'Event' entity (or whatever your Entity is named).
Instead I broke out this into three lines and the debugger displayed a lot more information:
Event *event = [[NSManagedObject alloc] init];
NSManagedObjectContext *moc = [self managedObjectContext];
event = [NSEntityDescription insertNewObjectForEntityForName:#"Event" inManagedObjectContext:moc];
I found I had not set the correct Type for one of the attributes and I had a typo in my code, all of which the debugger pointed out.
During my development, I could not find Entities that I added later on.
What worked for me: (Basically a sanity-tip)
Uninstall the App EVERY TIME you change the Data Model!
The Data Model is cached by Core Data between installs, to make sure the integrity stays in tact. Delete the App from the simulator / iPhone to be able to test your changes.
PS: does anyone know how to do that automatically?
TechZen is spot on...in my case it was #4. Walk through the steps in the following link and this should help you add the appropriate CoreData methods into an existing project and get everything set up correctly so you don't run into the error you're having.
Adding Core Data To Existing iPhone Projects

EXC_MEMORY_ACCESS when trying to delete from Core Data ($cash solution)

I have an application that downloads an xml file, parses the file, and creates core data objects while doing so. In the parse code I have a function called 'emptydatacontext' that removes all items from Core Data before creating replacements items from the xml data. This method looks like this:
-(void) emptyDataContext
{
NSFetchRequest * allCon = [[NSFetchRequest alloc] init];
[allCon setEntity:[NSEntityDescription entityForName:#"Condition" inManagedObjectContext:managedObjectContext]];
NSError * error = nil;
NSArray * conditions = [managedObjectContext executeFetchRequest:allCon error:&error];
DebugLog(#"ERROR: %#",error);
DebugLog(#"RETRIEVED: %#", conditions);
[allCon release];
for (NSManagedObject * condition in conditions) {
[managedObjectContext deleteObject:condition];
}
// Update the data model effectivly removing the objects we removed above.
//NSError *error;
if (![managedObjectContext save:&error]) {
DebugLog(#"%#", [error domain]);
}
}
The first time this runs it deletes all objects and functions as it should - creating new objects from the xml file. I created a 'update' button that starts the exact same process of retrieving the file the proceeding with the parse & build. All is well until its time to delete the core data objects. This 'deleteObject' call creates a "EXC_BAD_ACCESS" error each time. This only happens on the second time through.
Captured errors return null. If I log the 'conditions' array I get a list of NSManagedObjects on the first run. On the second this log request causes a crash exactly as the deleteObject call does.
I have a feeling it is something very simple I'm missing or not doing correctly to cause this behavior. The data works great on my tableviews - its only when trying to update I get the crashes.
I have spent days & days on this trying numerous alternative methods. Whats left of my hair is falling out. I'd be willing to ante up some cash for anyone willing to look at my code and see what I'm doing wrong. Just need to get past this hurdle.
Thanks in advance for the help!
Did you save the context after removing the objects in the for loop? Be aware that deleteObject: does not delete the object immediately, it simply schedule it for deletion when changes are committed, i.e., when you save the context.
EDIT: Your problem may be related to how you present your data to the user on your table view. Without additional code is difficult to tell exactly (are you using NSFetchedResultsController or not?), but my guess is that the interaction between deleting the data and showing them on the table is not correct. Probably, what is happening is that your table is told to visualize your data, but then, when you delete them, you are not updating correctly the table.
Wow, so after a few days of testing I went down a path that led me to tracking down Zombies & memory mgmt. This was a situation where the errors I was recieving were leftover from issues in another area. When parsing the data and placing it into Core Data I released an object that was to be autoreleased. So any subsequent calls to that item within core data (save, deletec, etc) caused a crash. Thank you all for your help and I vow to pay closer attention with my memory mgmt calls.