当我在删除单个对象后对上下文调用 undo 时,一切都按预期工作。但是如果用户删除了一个对象,然后又删除了另一个对象,那么无论用户请求撤消多少次,撤消都只会恢复第二个对象,就好像 undoLevels 设置为 1。无论 undoLevels 是否默认为 0(无限制)或明确设置为 6 作为测试。
此外,如果单个操作删除了多个对象,那么之后调用 undo 是没有效果的;没有对象被恢复。我尝试用 begin/endUndoGrouping 明确地将删除循环括起来,但无济于事。undoManager 的 groupsByEvent 为 YES(默认情况下),但无论我调用直接 undo 还是 undoNestedGroup 都没有区别。
每次操作后是否以某种方式保存上下文?不,因为如果我在运行这些测试后退出并重新启动应用程序,所有对象仍然存在于数据库中。
我错过了什么?
好的,你想要代码。以下是我认为最相关的内容:
上下文获取器:
- (NSManagedObjectContext *) managedObjectContextMain {
if (managedObjectContextMain) return managedObjectContextMain;
NSPersistentStoreCoordinator *coordinatorMain = [self persistentStoreCoordinatorMain];
if (!coordinatorMain) {
// present error...
return nil;
}
managedObjectContextMain = [[NSManagedObjectContext alloc] init];
[managedObjectContextMain setPersistentStoreCoordinator: coordinatorMain];
// Add undo support. (Default methods don't include this.)
NSUndoManager *undoManager = [[NSUndoManager alloc] init];
// [undoManager setUndoLevels:6]; // makes no difference
[managedObjectContextMain setUndoManager:undoManager];
[undoManager release];
// ...
return managedObjectContextMain;
}
多对象删除方法(由模态面板上的按钮调用):
/*
NOTE FOR SO:
SpecialObject has a to-one relationship to Series.
Series has a to-many relationship to SpecialObject.
The deletion rule for both is Nullify.
Series’ specialObject members need to be kept in a given order. So Series has a transformable attribute, an array of objectIDs, used to prepare a transient attribute, an array of specialObjects, in the same order as their objectIDs.
*/
- (void) deleteMultiple {
Flixen_Foundry_AppDelegate *appDelegate = [[NSApplication sharedApplication] delegate];
NSManagedObjectContext *contextMain = [appDelegate managedObjectContextMain];
NSUndoManager *undoMgr = [contextMain undoManager];
[undoMgr beginUndoGrouping];
// Before performing the actual deletion, drop the seln in the locator table.
[appDelegate.objLocatorController.tvObjsFound deselectAll:self];
// Get the indices of the selected objects and enumerate through them.
NSIndexSet *selectedIndices = [appDelegate.objLocatorController.tvObjsFound selectedRowIndexes];
NSUInteger index = [selectedIndices firstIndex];
while (index != NSNotFound) {
// Get the obj to be deleted and its series.
SpecialObject *sobj = [appDelegate.objLocatorController.emarrObjsLoaded objectAtIndex:index];
Series *series = nil;
series = sobj.series;
// Just in case...
if (!series) {
printf("\nCESeries' deleteMultiple was called when Locator seln included objs that are not a part of a series. The deletion loop has therefore aborted.");
break;
}
// Get the obj's series index and delete it from the series.
// (Series has its own method that takes care of both relnshp and cache.)
NSUInteger uiIndexInSeries = [series getSeriesIndexOfObj:sobj];
[series deleteObj:sobj fromSeriesIndex:uiIndexInSeries];
// Mark the special object for Core Data deletion; it will still be a non-null object in emarrObjsLoaded (objLocatorController’s cache).
[contextMain deleteObject:sobj];
// Get the next index in the set.
index = [selectedIndices indexGreaterThanIndex:index];
}
[undoMgr endUndoGrouping];
// Purge the deleted objs from loaded, which will also reload table data.
[appDelegate.objLocatorController purgeDeletedObjsFromLoaded];
// Locator table data source has changed, so reload. But end with no selection. (SeriesBox label will have been cleared when Locator seln was dropped.)
[appDelegate.objLocatorController.tvObjsFound reloadData];
// Close the confirm panel and stop its modal session.
[[NSApplication sharedApplication] stopModal];
[self.panelForInput close];
}
这是从其关系和有序缓存中删除对象的 Series 方法:
/**
Removes a special object from the index sent in.
(The obj is removed from objMembers relationship and from the transient ordered obj cache, but it is NOT removed from the transformable array of objectIDrepns.)
*/
- (void) deleteObj:(SpecialObject *)sobj fromSeriesIndex:(NSUInteger)uiIndexForDeletion {
// Don't proceed if the obj is null or the series index is invalid.
if (!sobj)
return;
if (uiIndexForDeletion >= [self.emarrObjs count])
return;
// Use the safe Core Data method for removing the obj from the relationship set.
// (To keep it private, it has not been declared in h file. PerformSelector syntax here prevents compiler warning.)
[self performSelector:@selector(removeObjMembersObject:) withObject:sobj];
// Remove the obj from the transient ordered cache at the index given.
[self.emarrObjs removeObjectAtIndex:uiIndexForDeletion];
// But do NOT remove the obj’s objectID from the transformable dataObjIDsOrdered array. That doesn't happen until contextSave. In the meantime, undo/cancel can use dataObjIDsOrdered to restore this obj.
}
这是由 comm-z undo 调用的方法及其后续操作:
- (void) undoLastChange {
Flixen_Foundry_AppDelegate *appDelegate = [[NSApplication sharedApplication] delegate];
NSManagedObjectContext *contextMain = [appDelegate managedObjectContextMain];
// Perform the undo. (Core Data has integrated this functionality so that you can call undo directly on the context, as long as it has been assigned an undo manager.)
// [contextMain undo];
printf("\ncalling undo, with %lu levels.", [contextMain.undoManager levelsOfUndo]);
[contextMain.undoManager undoNestedGroup];
// Do cleanup.
[self cleanupFllwgUndoRedo];
}
- (void) cleanupFllwgUndoRedo {
Flixen_Foundry_AppDelegate *appDelegate = [[NSApplication sharedApplication] delegate];
NSManagedObjectContext *contextMain = [appDelegate managedObjectContextMain];
DataSourceCoordinator *dataSrc = appDelegate.dataSourceCoordinator;
// ...
// Rebuild caches of special managed objects.
// (Some managed objects have their own caches, i.e. Series' emarrObjs. These need to be refreshed if their membership has changed. There's no need to use special trackers; the context keeps track of these.)
for (NSManagedObject *obj in [contextMain updatedObjects]) {
if ([obj isKindOfClass:[Series class]] && ![obj isDeleted])
[((Series *)obj) rebuildSeriesCaches];
}
// ...
// Regenerate locator's caches.
[appDelegate.objLocatorController regenerateObjCachesFromMuddies]; // also reloads table
}
这是在撤消/唤醒后重新生成其缓存的系列方法:
- (void) rebuildSeriesCaches {
// Don't proceed if there are no stored IDs.
if (!self.dataObjIDsOrdered || [self.dataObjIDsOrdered count] < 1) {
// printf to alert me, because this shouldn’t happen (and so far it doesn’t)
return;
}
NSMutableArray *imarrRefreshedObjIdsOrdered = [NSMutableArray arrayWithCapacity:[self.objMembers count]];
NSMutableArray *emarrRefreshedObjs = [NSMutableArray arrayWithCapacity:[self.objMembers count]];
// Loop through objectIDs (their URIRepns) that were stored in transformable dataObjIDsOrdered.
for (NSURL *objectIDurl in self.dataObjIDsOrdered) {
// For each objectID repn, loop through the objMembers relationship, looking for a match.
for (SpecialObject *sobj in self.objMembers) {
// When a match is found, add the objectID repn and its obj to their respective replacement arrays.
if ([[sobj.objectID URIRepresentation] isEqualTo:objectIDurl]) {
[imarrRefreshedObjIdsOrdered addObject:objectIDurl];
[emarrRefreshedObjs addObject:sobj];
break;
}
// If no match is found, the obj must have been deleted; the objectID repn doesn't get added to the replacement array, so it is effectively dropped.
}
}
// Assign their replacement arrays to the transformable and transient attrs.
self.dataObjIDsOrdered = imarrRefreshedObjIdsOrdered;
self.emarrObjs = emarrRefreshedObjs;
}
(我省略了 Locator 的 regenerateObjCachesFromMuddies,因为虽然我使用它的表来查看删除和撤消的结果,但我可以使用新的 fetch 重新加载表,完全重新生成表的缓存,并且这个测试仍然显示撤消不工作。)
像往常一样,将一个 SO 问题放在一起有助于解决问题,我现在意识到只要我使用不涉及互惠 SpecialObject-Series 关系的简单对象,撤消就可以正常工作。我在那里做错了什么......