I want to respond to changes of certain NSManagedObjects that have been added, updated, or deleted.
I have an issue with the deleted objects, though: all relationships to other objects are now nil.
Is there a way to get this kind of notification before the object is affected this way?
Edit:
This is basically my delete code:
[moc deleteObject:myObject];
id saveBlock = ^{
NSError *error = nil;
BOOL saved = NO;
saved = [self save:&error];
// error handling.
};
[moc performBlockAndWait:saveBlock];
If you are wanting to react to deletions then you should be listening for NSManagedObjectContextWillSaveNotification and watch for the NSDSeletedObjectsKey come through as part of the notification. That is the last chance before deletion to deal with them.
Related
It may be kinda naive but I was wondering if it is correct to use the following statement to delete a managed object from the persistent store of Core Data:
[managedObject.managedObjectContext deleteObject:managedObject];
I ran the above in Xcode debugger - it didn't complain but the object's content was still there. Could it be that context was referenced through the object to be deleted, and thus causing a memory lock preventing deletion of the object?
In regards to your content persisting, you still need to call save: on the context after deleting the object.
I can't answer specifically if you will have an issue by referencing the managedObjectContext in the managedObject as I usually use a 'DataManager' to manage my managedObjectContext. Below is an example of a delete method that I used in one of my dataManagers:
- (void)deleteReport:(Report*)aReport inContext:(NSManagedObjectContext*)context {
if (aReport != nil) {
if (context == nil) {
context = self.managedObjectContext;
}
context.mergePolicy = NSMergeByPropertyStoreTrumpMergePolicy;
[context deleteObject:aReport];
NSError *error = nil;
[context save:&error];
if (error) {
//NSLog(#"%#", error);
}
}}
EDIT: For clarification, the Report in this method is an instance of NSManagedObject, and the method takes NSManagedObjectContext as a parameter, because the application that it was pulled from supports the use of multiple contexts.
I use lib MagicalRecord (https://github.com/magicalpanda/MagicalRecord) for CoreData.framework.
I don't understand how to work with temporary objects.
How to create NSManagedContext for temporary objects and whether to delete each NSManagedObject after closing controller?
All objects created on a context are temporary objects and they become permanent when you save that context. So to discard them, you just don't save that context.
To create a new (temporary) context assuming you use Apple's Core Data Stack:
NSManagedObjectContext *tempChildContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
tempChildContext.parentContext = self.appDelegate.managedObjectContext;
To save changes, you need to do two saves, one on the temporary context and then push it into the main context.
[tempChildContext performBlock:^{
// do something that takes some time asynchronously using the temp context
// push to parent
NSError *error;
if (![tempChildContext save:&error])
{
// handle error
}
// save parent to disk asynchronously
[self.appDelegate.managedObjectContext performBlock:^{
NSError *error;
if (![self.appDelegate.managedObjectContext save:&error])
{
// handle error
}
}];
}];
I am sorry, I don't remember how to do it with MagicalRecord, but MR is just a wrapper around CoreData, so it will work. I stopped using MR on my first CoreData project. I suggest you read this: Multi-Context CoreData.
I'm following a guid in core data, and they implement an action method to preform saving to the database using a ManagedObject. I understand all the code in the method except the method which they say preform the saving, and to me it looks like the method checks if there is an error, and if yes so there is an NSLog to print that there was an error. this is the method:
- (IBAction)save:(id)sender {
NSManagedObjectContext *context = [self managedObjectContext];
// creating a new managed object
NSManagedObject *newDevice = [NSEntityDescription insertNewObjectForEntityForName:#"Device" inManagedObjectContext:context];
[newDevice setValue:self.nameTextField.text forKey:#"name"];
[newDevice setValue:self.versionTextField.text forKey:#"version"];
[newDevice setValue:self.companyTextField.text forKey:#"company"];
NSError *error = nil;
if (![context save:&error]) {
NSLog(#"Can't Save! %# %#", error, [error localizedDescription]);
}
[self dismissViewControllerAnimated:YES completion:nil];
}
Obviously something happens in [context save:&error] this call which I'd love if you can explain what?
Calling save: persists changes made to the object graph on the specific context and takes it one level above.
Each context contains its own changeset, and when you call save:, the changes are either taken one level above (to its parent context), or, if there is no parent context, to the store coordinator to be persisted by the method specified when opening the coordinator (SQLite, XML, binary, etc.).
Changes can be modifications, insertions or deletions.
Before saving, changes to objects are verified and objects are notified about the save process.
After saving, notifications are sent to the system to let know various components (such as fetch results controllers, your code, etc.) that the save operation has taken place.
I am using Core Data to store a User entity which has a to many relationship to another entity (ScheduleDay). Every time I sync I use this line to reset the Ordered Set:
self.user.scheduleDays = [NSOrderedSet orderedSet];
My question is are the old days being deleted, or is there a better way to make sure I delete the old days as to not clutter up the NSManagedObjectContext or save too much unnecessary data.
No, this will not automatically delete these objects.
You have to manually delete them yourself prior to setting the scheduleDays to a new set.
To delete all the scheduleDays entities, you could do this for example:
NSManagedObjectContext *context = self.user.managedObjectContext;
for (NSManagedObject *object in self.user.scheduleDays)
{
[context delete:object];
}
self.user.scheduleDays = [NSOrderedSet orderedSet];
NSError *error;
[context save:&error];
if (error)
{
// Handle error
}
My app is using an NSFetchedResultsController tied to a Core Data store and it has worked well so far, but I am now trying to make the update code asynchronous and I am having issues. I have created an NSOperation sub-class to do my updates in and am successfully adding this new object to an NSOperationQueue. The updates code is executing as I expect it to and I have verified this through debug logs and by examining the SQLite store after it runs.
The problem is that after my background operation completes, the new (or updated) items do not appear in my UITableView. Based on my limited understanding, I believe that I need to notify the main managedObjectContext that changes have occurred so that they may be merged in. My notification is firing, nut no new items appear in the tableview. If I stop the app and restart it, the objects appear in the tableview, leading me to believe that they are being inserted to the core data store successfully but are not being merged into the managedObjectContext being used on the main thread.
I have included a sample of my operation's init, main and notification methods. Am I missing something important or maybe going about this in the wrong way? Any help would be greatly appreciated.
- (id)initWithDelegate:(AppDelegate *)theDelegate
{
if (!(self = [super init])) return nil;
delegate = theDelegate;
return self;
}
- (void)main
{
[self setUpdateContext:[self managedObjectContext]];
NSManagedObjectContext *mainMOC = [self newContextToMainStore];
NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
[center addObserver:self
selector:#selector(contextDidSave:)
name:NSManagedObjectContextDidSaveNotification
object:updateContext];
[self setMainContext:mainMOC];
// Create/update objects with mainContext.
NSError *error = nil;
if (![[self mainContext] save:&error]) {
DLog(#"Error saving event to CoreData store");
}
DLog(#"Core Data context saved");
}
- (void)contextDidSave:(NSNotification*)notification
{
DLog(#"Notification fired.");
SEL selector = #selector(mergeChangesFromContextDidSaveNotification:);
[[delegate managedObjectContext] performSelectorOnMainThread:selector
withObject:notification
waitUntilDone:YES];
}
While debugging, I examined the notification object that is being sent in contextDidSave: and it seems to contain all of the items that were added (excerpt below). This continues to make me think that the inserts/updates are happening correctly but somehow the merge is not being fired.
NSConcreteNotification 0x6b7b0b0 {name = NSManagingContextDidSaveChangesNotification; object = <NSManagedObjectContext: 0x5e8ab30>; userInfo = {
inserted = "{(\n <GCTeam: 0x6b77290> (entity: GCTeam; id: 0xdc5ea10 <x-coredata://F4091BAE-4B47-4F3A-A008-B6A35D7AB196/GCTeam/p1> ; data: {\n changed =
The method that receives your notification must indeed notify your context, you can try something like this, which is what I am doing in my application:
- (void)updateTable:(NSNotification *)saveNotification
{
if (fetchedResultsController == nil)
{
NSError *error;
if (![[self fetchedResultsController] performFetch:&error]) {
//Update to handle the error appropriately.
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
exit(-1); // Fail
}
}
else
{
NSManagedObjectContext *context = [fetchedResultsController managedObjectContext];
// Merging changes causes the fetched results controller to update its results
[context mergeChangesFromContextDidSaveNotification:saveNotification];
// Reload your table view data
[self.tableView reloadData];
}
}
Hope that helps.
Depending on the specifics of what you are doing, you may be going about this the wrong way.
For most cases, you can simply assign a delegate using NSFetchedResultsControllerDelegate. You provide an implementation for one of the methods specified in "respondingToChanges" depending on your needs, and then send the tableView a reloadData message.
The answer turned out to be unrelated to the posted code which ended up working as I expected. For reasons that I am still not entirely sure of, it had something to do with the first launch of the app. When I attempted to run my update operation on launches after the Core Data store was created, it worked as expected. I solved the problem by pre-loading a version of the sqlite database in the app so that it did not need to create an empty store on first launch. I wish I understood why this solved the problem, but I was planning on doing this either way. I am leaving this here in the hope that someone else may find it useful and not lose as much time as I did on this.
I've been running into a similar problem in the simulator. I was kicking off an update process when transitioning from the root table to the selected folder. The update process would update CoreData from a web server, save, then merge, but the data didn't show up. If I browsed back and forth a couple times it would show up eventually, and once it worked like clockwork (but I was never able to get that perfect run repeated). This gave me the idea that maybe it's a thread/event timing issue in the simulator, where the table is refreshing too fast or notifications just aren't being queued right or something along those lines. I decided to try running in Instruments to see if I could pinpoint the problem (all CoreData, CPU Monitor, Leaks, Allocations, Thread States, Dispatch, and a couple others). Every time I've done a "first run" with a blank slate since then it has worked perfectly. Maybe Instruments is slowing it down just enough?
Ultimately I need to test on the device to get an accurate test, and if the problem persists I will try your solution in the accepted answer (to create a base sql-lite db to load from).