0
votes

I am using a UIManagedDocument for core data. I have Calendar objects in my managedObjectContext that have a calendarSelected attribute. The Calendar entity has a to-many relationship to CalendarEntry.

When I change their calendarSelected attribute and then perform a NSFetchRequest to get CalendarEntry objects with the following predicate:

[NSPredicate predicateWithFormat:@"calendar.calendarSelected = YES"]

the calendar.calendarSelected does not seem to be seeing the change I made without me calling

[myManagedDocument saveToURL:myManagedDocument.fileURL forSaveOperation:UIDocumentSaveForOverwriting completionHandler:^(BOOL success) {}];

first. I read somewhere that fetching things from the same context should honor changes made to in that context even if the changes had not been written to the persistent store. What am I missing?

Update:

It appears to be happening when the calendarEvents relationships is a fault: calendarEvents = "<relationship fault: 0x91aec90 'calendarEvents'>"; but works when the relationship is not a fault.

2
Does it happen for all changes or just new ones?Jody Hagins
I'm not sure. I will try to find out. See my edit for more info.offex

2 Answers

1
votes

If the issue occurs only on newly-inserted CalendarEntry objects pointed to by the to-many relationship, then it's possible that they don't yet have permanent object ids. This is easy to debug via just dumping the object id and checking to see if it's temporary or permanent.

I've seen this happen when the containing object in the to-many relationship is retained; it seems that so long as it is retained, the to-many contained objects in the relationship never get to a point wherein they obtain permanent ids. Somewhat easy to debug by putting the application in the background and restarting it; the backgrounding will typically force the UIManagedDocument to save, and things will start working as you expect thereafter, as the CalendarEntry entities will have been assigned permanent ids and thus will become fetchable.

So far as saving the UIManagedDocument, you don't have control over when that'll happen when using a UIManagedDocument. The best thing to do is schedule a save to occur in the future, via updateChangeCount:UIDocumentChangeDone, but again, it'll happen 'soon', but not deterministically, i.e., it's not possible to know when it'll happen.

To resolve the temporary vs. permanent object id issue, if that's what you're seeing, try the following category on NSManagedObjectContext; call it after you've completed inserting new CalendarEntry objects, in order to force the issue.

// Obtain permanent ids for any objects that have been inserted into
// this context.
//
// As of this writing (iOS 5.1), we need to manually obtain permanent
// ids for any inserted objects prior to a save being performed, or any
// objects that are currently retained, that have relationships to the
// inserted objects, won't be updated with permanent ids for the related
// objects when a save is performed unless the retained object is refetched.
// For many objects, that will never occur, so we need to force the issue.

- (BOOL)obtainPermanentIDsForInsertedObjects:(NSError **)error
{
    NSSet * inserts = [self insertedObjects];

    if ([inserts count] == 0) return YES;

    return  [self obtainPermanentIDsForObjects:[inserts allObjects]
                                         error:error];
}
0
votes

Basically, after making any changes, you must either use an undo manager, or call [document updateChangeCount:UIDocumentChangeDone]; and THAT'S ALL! Any other "save" calls will just break something else down the line somewhere.

The canonical method of updating should be...

You know you are on main thread

NSManagedObjectContext *ctx = document.managedObjectContext;
// Do stuff with it
[document updateChangeCount:UIDocumentChangeDone];

You are on a different thread

[document.managedObjectContext performBlock:^{
    // Do stuff with it on the main thread
    [document updateChangeCount:UIDocumentChangeDone];
}];

OR

NSManagedObjectContext *moc = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
moc.parentContext = document.managedObjectContext;
[moc performBlock:^{
    // Do stuff with it on a background thread
    // Push changes up to parent (which is the document's main MOC)
    // The document will know it's dirty and save changes
    [moc save:&error];
}];

OR if you want to make changes in a background thread, without messing with the main document MOC, do them on the parent... the main MOC will not see them until the next fetch.

[document.parentContext.managedObjectContext performBlock:^{
    // Do stuff with it on a background thread, but the main MOC does not
    // see the changes until it does a fetch.
}];