I'm working on an ipad application that use coredata. It download information on a database that is on the web, and record them in coredata. The application is based on a split view. My problem was to make the update of the data in background. Here is how I've done :
- I've create an NSOperation, that does the download and the update of the data.
- This NSOperation use a different NSManagedObjectContext than the context of the appDelegate, return by this function, that is in the appDelegate :
(NSManagedObjectContext*)newContextToMainStore {
NSPersistentStoreCoordinator *coord = nil;
coord = [self persistentStoreCoordinator];
NSManagedObjectContext *moc = [[NSManagedObjectContext alloc] init];
[moc setPersistentStoreCoordinator:coord];
return [moc autorelease];
}
- I've had an observer in the NSOperation, that will call this function in the appDelegate when I save the context, to modify the context of the delegate too :
- (void)mergeChangesFromContextSaveNotification:(NSNotification*)notification {
[[self managedObjectContext]mergeChangesFromContextDidSaveNotification:notification];
}
But I've a problem : when I delete an element in the rootViewController, I really don't know how to manage the changes in the background process, because there is a loop inside that browse all the entities : if I delete one entitie when the background loop is at the same entitie, this is... really bad...
My solution was just to stop the update process when I delete an entitie, and then restart it, simply... But I've realised that the changes made in the "main" context were not apply in the new context I just create for the update.
So I ask you : Why the changes aren't apply in the new context ? If this the wrong way, how do you do this ? Using mergeChangesFromContext or something else ?
Thanks you in advance.
Sorry, my mistakes :
- First, my entities contained other entities, and because of a bad relationship, the entities that was contained in the parent entitie weren't deleted.
- Second, I was thinking that call -cancelAllOperations will stop the current operation, but it's not, you have to check in the nsoperation if the process is cancelled with [self isCancelled].
That's all !
U don't need to take care if somebody will delete something from interface, bcs NSArrayController is thread safe. But if u make updates and delete in same time, u have to take care about it.
Related
I already have a tableView with data in it. IF you tap a cell/row it pushes to an edit type of view. Is there anyway to edit core data's data other than: By edit, i mean i already have data inserted into my context. I have loaded my data into my view, the user can change the existing data, and re save it.
.h
//Below is the entity/entity's class name 'Amm'
Amm *amm;
.m
-(IBAction)save
{
[self.amm setValue:self.nameField.text forKey:#"name"];
[self.amm setValue:self.nicknameField.text forKey:#"nickname"];
[self.navigationController popViewControllerAnimated:YES];
NSError *error;
if (![self.managedObjectContext save:&error]) {
//Handle Error
}
}
I want this code to work, however the design pattern of my app isnt allowing this code to work for me as it does in other parts of my app. Thank you very much for any and all help!
I assume from what you've said you have:
A table view listing your managed objects
A view where you can edit the values of a managed object
A save button bound to the save method
What's the actual issue? I'm assuming when you tap save that:
The values in self.nameField.text isn't setting self.amm.name
The values in self.nicknameField.text isn't setting self.amm.nickname
Is that right? If so perhaps try the following code to set the managed object values:
self.amm.name = self.nameField.text
self.amm.nickname = self.nicknameField.text
If that's not the issue and you are actually setting the managed object values properly, is it that you just need to refresh the table-view? Perhaps use some NSLog commands to log every step of the applications progress.
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";
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)
For example:
I have two entities named Projectand Todo where a project has many todos (to-many relationship) and each todo has one Project(see image).
In my ViewController.h I have something like:
...
NSArray *projectArray;
NSArray *todosArray;
#property (nonatomic,retain) NSArray *projectArray;
#property (nonatomic,retain) NSArray *todosArray;
...
In my ViewController.m I have something like:
...
#synthesize projectArray,todosArray;
...
self.projectArray = [self fetchRequestForAllProjects];
...
The user has an interface where he is able to select between all different projects. As soon as the user selects a project, the related todo objects have to be set to be loaded and presented.
Question 1: How do I load the set of todos into the todosArray in the best way?
I was doing it like that (also in the ViewController.m):
...
// after deselecting a project entry I reset the todosArray
self.todosArray = nil;
...
//when the user selects a new project I reset the todosArray like this:
self.todosArray = [selectedProject.todos allObjects];
...
But somehow the app very rarely crashes in the last line of code. Is there any problem with my code?
Question 2: Would it be better to use another fetch request for the todos Objects?
UPDATE:
I am using the todosArrayin various methods of the ViewController.m:
(a) get the count of objects,
(b) present each todos entry inside a table view row, and
(c) to identify the selected todo entry (threw row selection)
Answer #1
It is best to sort them when you pull everything out of the set into an array. This will keep your user experience consistent:
NSSet *projectTodoEntities = [mySelectedProject valueForKey:#"todos"];
NSSortDescriptor *sorter = [[NSSortDescriptor alloc] initWithKey:#"myKey" ascending:YES];
NSArray *sortedToDos = [projectTodoEntities sortedArrayUsingDescriptors:[NSArray arrayWithObject:sorter]];
Answer #2
No, fetching is expensive compared to just accessing a relationship. A fetch will hit disk, etc.
For answer #1.
1). Please make sure whether selectedProject is deleted on other thread, if it is deleted, core data will mark this NSManagedObject as invalid, when you try to access property of this object, a NSObjectInaccessibleException will be thrown.
2). All NSManagedObject associates NSManagedObjectContext, the context is limited on certain thread or thread queue, when you access "todos" relationship while it is in fault state, it will trigger a fetching from persistent store, you must make sure whether execution thread is valid for NSManagedObjectContext, otherwise you should use below code.
NSManagedObjectContext *context = [selectedProject managedObjectContext];
__weak YouControllerClass *weakSelf;
[context performBlockAndWait:^{
weakSelf.todosArray = [selectedProject.todos allObjects];
}];
Answer #2: Would it be better to use an other fetch request for the todos Objects?
By default the "todos" relationship is returned as fault state, when you access project property "todos:, it actually triggers fetching from persistent store for 1st time, core data may cache these "todos" objects in memory later, so you will get fast access in future (unless you reset NSManagedObjectContext)
For most scenarios, like user checks his limit todo lists, it is ok to trigger another fetch request, the performance is not real problem if there is no huge blob data in todo object.
For performance critical scenarios, like use core data to save hundreds of photos and metadata as a relationship, when you draws all these photos on UIView based on height, width or URL property of photo object, you make consider pre-fetching photo meta to avoid performance hit (io operation).
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