save: on a NSManagedObjectContext not working

2020-08-02 07:27发布

问题:

In a navigation controller, I go from view controller 1 to view controller 2.

View Controller 1 is a table view controller tied to a FetchedResultsController. It fetches data from a specific context and displays it. I then segue to view controller 2 if a row is tapped. While segueing, I set a particular NSManagedObject property of view controller 2 using the data I have in view controller 1.

Now, in view controller 2 I am able to show data using the NSManagedObject property and then also make changes to it and perform a save:. When I go back to view controller 1 , the change is reflected. However, if I re-launch the app it is not being reflected any more in any of the view controllers.

This is how I am doing the save.

- (void)hiddenAttributeSwitchSlid:(UISwitch *)sender
{
    [self.workoutType.managedObjectContext performBlock:^{
        self.workoutType.workoutHiddenByDefault = [NSNumber numberWithBool:sender.isOn];
        NSError *error = nil;
        if (![self.workoutType.managedObjectContext save:&error]) {
            NSLog(@"There was an error in saving to context - %@", [error localizedDescription]);
        }
        else {
            NSLog(@"No error");
        }
    }];
}

workoutType is a NSManagedObject which is set in the prepareForSegue: before segueing to this view controller.

Even if I do not use the performBlock:, it doesn't work.

PS - I know questions of this kind have been asked before. I browsed through them, but nothing seems to be working.

回答1:

Do you have a default value set for that attribute in your model?

There is an identified bug in Core Data where in some situations (I have not narrowed it all the way down) where an attribute with a default value gets reset several times during the backgrounding of an application.

The way to test for this is to listen for the value being changed via KVO and log the changes then duplicate your test. If you see several changes then you know you are hitting that bug.

The only known solution I have seen that is reliable is to remove the default value. If a default value is required then I would add it to the NSManagedObject subclass in the -awakeFromInsert method and then update the validation methods to check for it. Sucks I know.

Update #2

How many contexts do you have?

Are you using parent child contexts?

If so, are you saving the top most parent?

Update #3

Ok, a UIManagedDocument has two NSManagedObjectContext instances inside of it. Is there a reason you are using a UIManagedDocument? Do you have more than one document in your application? If you don't then I strongly suggest you switch back to a traditional Core Data stack. The UIManagedDocument is not really meant to be in a single stack application.

As for the saving issue directly, UIManagedDocument tries to save in the background when you exit the application. If can take a bit of time and, personally, is not very reliable. You can request a save on exit which will help to insure the save is speedy, but even then it can be unreliable.

How are you exiting the application?

Are you killing it in Xcode?

Are you backgrounding it and then resuming it?

Are you backgrounding it and then killing it from the iOS device?

You can listen for the UIManagedDocument to save and then print a log statement so you can see when the saves actually happen to disk. That might be useful to help narrow down exactly when it is and is not saving.



回答2:

I think you shouldn't use saving on context, the document will auto save.

There might be a data consistency error when the document is being saved. I propose to you not to use saving of the context by [self.workoutType.managedObjectContext save:&error] and use the following class inherited from UIManagedDocument which adds logging on auto-save:

LoggingManagedDocument.h:

@interface LoggingManagedDocument : UIManagedDocument

@end

LoggingManagedDocument.m:

#import "LoggingManagedDocument.h"

@implementation LoggingManagedDocument

- (id)contentsForType:(NSString *)typeName error:(NSError *__autoreleasing *)outError
{
    NSLog(@"Auto-Saving Document");
    return [super contentsForType:typeName error:outError];
}

- (void)handleError:(NSError *)error userInteractionPermitted:(BOOL)userInteractionPermitted
{
    NSLog(@"error: %@ userInfo: %@", error, error.userInfo);
}

@end

This class should help you identify the problem, why your data is not saved. Just let the application run and wait 15-30 seconds after you make the change of the attribute and auto-save should occur.

The logging is based on: http://blog.stevex.net/2011/12/uimanageddocument-autosave-troubleshooting/



回答3:

From the UIManageDocument documentation:

The UIManagedDocument architecture leads to several considerations:

You should typically use the standard UIDocument methods to save the document. If you save the child context directly, you only commit changes to the parent context and not to the document store. If you save the parent context directly, you sidestep other important operations that the document performs.

The easiest option is to use a saveless model, which means using the NSUndoManager of the document. I usually do

[self.workoutType.managedObjectContext.undoManager beginUndoGrouping];
before adding the changes and
[self.workoutType.managedObjectContext.undoManager endUndoGrouping];
after editing.



回答4:

You need to save the document:

[document saveToURL:document.fileURL forSaveOperation:UIDocumentSaveForOverwriting completionHandler:NULL];


标签: ios core-data