当我在删除单个对象之后在上下文上调用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

}


这是在撤消/唤醒后重新生成其缓存的series方法:

- (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,因为尽管我正在使用其表查看删除和撤消的结果,但是我可以使用新的提取重新加载表,从而完全重新生成表的缓存,并且此测试仍然表明撤消无法正常工作。)

像往常一样,将SO问题组合在一起的任务就可以解决问题,现在我意识到,只要使用不涉及互惠的SpecialObject-Series关系的简单对象,撤消就可以正常工作。我在那儿做错了...

最佳答案

我认为您正在与自定义撤消内容和Core Data的自动支持进行斗争。

在正常的撤消/重做代码中,您具有可撤消的漏斗点。通常,一个不可撤消的加和其反向撤消删除。调用一个将另一个注册为相反的操作,反之亦然。用户撤消/重做然后在它们之间来回切换。您将“用户创建了一个新的Foo”代码与“现在将这个foo毫无疑问地添加到集合中”代码分开了(这样,“删除Foo”和“ add Foo”的工作独立于提供一个新创建的Foo)。

对于Core Data,添加和删除意味着“插入上下文并从上下文中删除”。另外,您仍然需要自定义渠道方法,因为(在您的情况下)您正在做其他事情(更新缓存)。这对于使用Foo来说很容易,但是当您想要操纵在一个动作中创建的Foo / Bar程序集之间的关系时会发生什么呢?

如果创建Foo使用它创建了一些Bar,那将是一回事(-awakeFromInsert等),因为您只需要处理更新缓存(可以通过键/值来完成)观察变化的环境)。由于创建Foo似乎可以与现有Bar(已经在上下文中)建立关系,因此在尝试与CD的内置撤消支持合作时会遇到困难。

如果您使用内置的Core Data撤消/重做支持,在这种情况下没有简单的解决方案。在这种情况下,您可以do as this post suggests并将其关闭。然后,您可以完全自己处理撤消/重做操作……但是您将需要编写大量代码来观察对象是否改变了有趣的属性,为每个对象注册了相反的操作。

尽管这不能解决您的问题,但我希望它至少指出您要执行的操作的复杂性,并为您提供前进的可能。如果不了解很多关于您的模型的知识(至少在概念级别上)以及您的UI如何向用户展示,就很难给出具体的架构建议。

我希望我在这种情况下错了-也许其他人可以给您更好的答案。 :-)

关于objective-c - 无法撤消一项以上的操作,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/7837533/

10-13 08:44