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.
Related
I have a pretty simple setup.
I have a NSTableView within my MainWindow.xib, whose value is bound to an ArrayController like so:
The ArrayController is setup like this:
I have a TestModel.xcdatamodeld which contains one entity, Test, with one attribute, a body with type of string.
I then have my window set up simply like this, with a textfield and two buttons to add and remove from the array controller:
When I add or remove any entry into the tableview, it works fine. But when I close and restart my app, those changes aren't synced to the core data.
What am I doing wrong?
Thanks
Edit: I'm using Magical Record, too.
Did you save changes with the context?
Something like:
NSError *error = nil;
NSManagedObjectContext *managedObjectContext = self.managedObjectContext;
if (managedObjectContext != nil) {
if ([managedObjectContext hasChanges] && ![managedObjectContext 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.
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}
}
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 have an NSFetchedResultsController that queries on a Core Data entity, 'MyGalleryPhoto'.
I'm trying to delete some objects, and coming up against some problems. I'm using MagicalRecord. Here is my original attempt at the code, which in my view should work fine. At the point the code is run, the objects definitely exist, because they display in the fetchedResultsController.
[MagicalRecord saveInBackgroundWithBlock:^(NSManagedObjectContext *localContext) {
for (MyGalleryPhoto *myGalleryPhoto in [self.fetchedResultsController.fetchedObjects objectsAtIndexes: self.selectedIndexes]) {
NSError *error = nil;
MyGalleryPhoto *localMyGalleryPhoto = (MyGalleryPhoto *) [localContext existingObjectWithID: myGalleryPhoto.objectID error: &error];
NSLog(#"error: %#:%#", [error localizedDescription], [error userInfo]);
NSLog(#"mygp: %#", [localMyGalleryPhoto description]);
[localMyGalleryPhoto deleteInContext: localContext];
}
} completion:^(void){
}];
This code does not work. The myGalleryPhoto entry is not found and the error returned is: "The operation couldn’t be completed. (Cocoa error 133000.)" I've also tried using MR_inContext, which just calls existingObjectWithId:error:.
After a lot of messing around I've come up with this vile frankenstein's monster, that gets all the records out of the entity and compares the string representations of the ObjectIDs. This works fine. Why? I'm using a copy of MagicalRecord I downloaded from GitHub today, XCode up to date, latest SDK, et cetera.
[MagicalRecord saveInBackgroundWithBlock:^(NSManagedObjectContext *localContext) {
NSArray *allMyGalleryPhotos = [MyGalleryPhoto findAllInContext: localContext];
for (MyGalleryPhoto *myGalleryPhoto in [self.fetchedResultsController.fetchedObjects objectsAtIndexes: self.selectedIndexes]) {
MyGalleryPhoto *myGalleryPhotoToDelete = nil;
for (MyGalleryPhoto *existingMyGalleryPhoto in allMyGalleryPhotos) {
NSString *existingURLString = [[existingMyGalleryPhoto.objectID URIRepresentation] absoluteString];
NSString *URLString = [[myGalleryPhoto.objectID URIRepresentation] absoluteString];
NSLog(#"ExistingURLString: %#", existingURLString);
NSLog(#"URLString: %#", URLString);
if ([URLString isEqualToString: existingURLString]) {
myGalleryPhotoToDelete = existingMyGalleryPhoto;
}
}
if (myGalleryPhotoToDelete) [myGalleryPhotoToDelete deleteInContext: localContext];
}
} completion:^(void){
}];
Cocoa Error 13000 is a Referential Integrity error, as described in the documentation. That means you're looking for an object that doesn't exist in the store. On a more practical level, that means that your contexts (I'm assuming you have more than one Managed Object Context) are not in sync. That is, you've added a new object to one context, while the other doesn't have that object because the previous context has not been saved.
Regarding your code, the first problem I see in the first example is that you are crossing thread boundaries at the very start. The fetchedResultsController has a reference to objects in another context (I'm going to assume the default context). Every time saveInBackground is called, it gives you a new context to use, but it also puts that block of code on a background thread. Crossing thread boundaries, even in the new version of Core Data, is going to give you crazy, hard to track down problems at random times.
The gist if what you're trying to do in the first (simpler) block of code is you have a collection of photo objects you want to remove from your application. I would do something like this instead:
NSPredicate *objectsToDelete = [NSPredicate predicateWithFormat:#"self in %#", self.fetchedResultsController.fetchedObjects];
[MagicalRecord saveInBackgroundWithBlock:^(NSManagedObjectContext *)localContext
{
[MyGalleryPhoto deleteAllMatchingPredicate:objectsToDelete inContext:localContext];
}];
The deleteAllMatchingPredicate method should do a lookup of the objects in the correct context (which you weren't doing in the first block of code) so they can be deleted. It also sets up the objects to load as faults, so we're not going to load everything in memory, only to delete it immediately. It'll load only what it needs, and no more.
I would not use existingObjectWithID: in this case. This method never loads faults. Your use case means it'll load the entire object into memory, only to delete it anyway.
I'm doing operations in a GCD dispatch queue on a NSManagedObjectContext defined like this:
- (NSManagedObjectContext *)backgroundContext
{
if (backgroundContext == nil) {
self.backgroundContext = [NSManagedObjectContext MR_contextThatNotifiesDefaultContextOnMainThread];
}
return backgroundContext;
}
MR_contextThatNotifiesDefaultContextOnMainThread is a method from MagicalRecord:
NSManagedObjectContext *context = [[self alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
[context setParentContext:[NSManagedObjectContext MR_defaultContext]];
return context;
After fetching my objects and giving them the correct queue position i log them and the order is correct. However, the second log seems to be completely random, the sort descriptor clearly isn't working.
I have narrowed down the Problem to [self.backgroundContext save:&error]. After saving the background context sort descriptors are broken.
dispatch_group_async(backgroundGroup, backgroundQueue, ^{
// ...
for (FooObject *obj in fetchedObjects) {
// ...
obj.queuePosition = [NSNumber numberWithInteger:newQueuePosition++];
}
NSFetchRequest *f = [NSFetchRequest fetchRequestWithEntityName:[FooObject entityName]];
f.predicate = [NSPredicate predicateWithFormat:#"queuePosition > 0"];
f.sortDescriptors = [NSArray arrayWithObject:[NSSortDescriptor sortDescriptorWithKey:#"queuePosition" ascending:YES]];
NSArray *queuedObjects = [self.backgroundContext executeFetchRequest:f error:nil];
for (FooObject *obj in queuedObjects) {
DLog(#"%# %#", obj.queuePosition, obj.title);
}
if ([self.backgroundContext hasChanges]) {
DLog(#"Changes");
NSError *error = nil;
if ([self.backgroundContext save:&error] == NO) {
DLog(#"Error: %#", error);
}
}
queuedObjects = [self.backgroundContext executeFetchRequest:f error:nil];
for (FooObject *obj in queuedObjects) {
DLog(#"%# %#", obj.queuePosition, obj.title);
}
});
I've got no idea why the sort descriptor isn't working, any Core Data experts want to help out?
Update:
The problem does not occur on iOS 4. I think the reason is somewhere in the difference between thread isolation and private queue modes. MagicalRecord automatically uses the new concurrency pattern which seems to behave differently.
Update 2:
The problem has been solved by adding a save of the background context:
if ([[NSManagedObjectContext MR_contextForCurrentThread] hasChanges]) {
DLog(#"Changes");
NSError *error = nil;
if ([[NSManagedObjectContext MR_contextForCurrentThread] save:&error] == NO) {
DLog(#"Error: %#", error);
} else {
NSManagedObjectContext *parent = [NSManagedObjectContext MR_contextForCurrentThread].parentContext;
[parent performBlockAndWait:^{
NSError *error = nil;
if ([parent save:&error] == NO) {
DLog(#"Error saving parent context: %#", error);
}
}];
}
}
Update 3:
MagicalRecord offers a method to recursively save a context, now my code looks like this:
if ([[NSManagedObjectContext MR_contextForCurrentThread] hasChanges]) {
DLog(#"Changes");
[[NSManagedObjectContext MR_contextForCurrentThread] MR_saveWithErrorHandler:^(NSError *error) {
DLog(#"Error saving context: %#", error);
}];
}
Shame on me for not using it in the first place...
However, I don't know why this helps and would love to get an explanation.
I'll try to comment, since I wrote MagicalRecord.
So, on iOS5, MagicalRecord is set up to try to use the new Private Queue method of multiple managed object contexts. This means that a save in the child context only pushes saves up to the parent. Only when a parent with no more parents saves, does the save persist to its store. This is probably what was happening in your version of MagicalRecord.
MagicalRecord has tried to handle this for you in the later versions. That is, it would try to pick between private queue mode and thread isolation mode. As you found out, that doesn't work too well. The only truly compatible way to write code (without complex preprocessor rules, etc) for iOS4 AND iOS5 is to use the classic thread isolation mode. MagicalRecord from the 1.8.3 tag supports that, and should work for both. From 2.0, it'll be only private queues from here on in.
And, if you look in the MR_save method, you'll see that it's also performing the hasChanges check for you (which may also be unneeded since the Core Data internals can handle that too). Anyhow, less code you should have to write and maintain...
The actual underlying reason why your original setup didn't work is an Apple bug when fetching from a child context with sort descriptors when the parent context is not yet saved to store:
NSSortdescriptor ineffective on fetch result from NSManagedContext
If there is any way you can avoid nested contexts, do avoid them as they are still extremely buggy and you will likely be disappointed with the supposed performance gains, cf. also:
http://wbyoung.tumblr.com/post/27851725562/core-data-growing-pains
Since CoreData isnot a safe-thread framework and for each thread(operation queue), core data uses difference contexts. Please refer the following excellent writing
http://www.duckrowing.com/2010/03/11/using-core-data-on-multiple-threads/