【问题标题】:UndoManager with async or long-running tasks带有异步或长时间运行任务的 UndoManager
【发布时间】:2022-01-03 01:54:37
【问题描述】:

我在将 UndoManager / NSUndoManager 用于异步或长时间运行的任务时遇到问题。我有一个可行的解决方案,但非常复杂——对于一个相当常见的问题来说,这远远超过了合理的解决方案。我会将其发布为答案,并希望有更好的答案。

问题一:

我的可撤消任务未在当前运行循环中完成。这样的任务可以是一个带有异步调用回调的简短操作。它也可以是一个长时间运行的操作,我可能会显示一个进度指示器,甚至提供取消选项。

问题 2:

我的可撤消任务可能会失败或被取消。或者更糟的是,重做任务可能会失败。示例:我移动了一个文件,撤消后我发现该文件已从新位置消失。我不应该将重做任务放回堆栈。

想法 1:

我可以在任务完成时进行撤消/重做注册。无法撤消尚未完成、已取消或已失败的操作。使用此设置,我无法正确配对操作及其撤消操作:重做不起作用。示例:用户要求复制文件。在复制操作结束时,我将操作注册到 UndoManager。用户选择撤消。我再次等到操作完成后向 UndoManager 注册。现在UndoManager并不知道刚刚完成的文件删除实际上是对前面复制操作的逆操作。它不是为用户提供重做副本的选项,而是提供撤消删除的选项

想法 2:

禁用自动撤消分组。我看不到如何通过长时间运行的操作来做到这一点。我想为大多数其他任务自动分组。

我无法使用带有 asnyc 回调的简单操作来实现它。此抛出:“endUndoGrouping 调用没有匹配的开始”

        let assets = PHAsset.fetchAssets(in: album, options: nil)
        let parent = PHCollectionList.fetchCollectionListsContaining(album, options: nil).firstObject

        if let undoManager = undoManager {
            undoManager.groupsByEvent = false
            undoManager.beginUndoGrouping()

            let isUndoManagerOperation = undoManager.isUndoing || undoManager.isRedoing
            let targetSelf = Controller.self as AnyObject

            undoManager.registerUndo(withTarget: targetSelf) { [weak undoManager] targetSelf in
                Controller.createAlbum(for: assets, title: album.localizedTitle, parent: parent, with: undoManager, completionHandler: nil)
            }

            if !isUndoManagerOperation {
                undoManager.setActionName(NSLocalizedString("Delete Album", comment: "Undoable action: Delete Album"))
            }
        }

        PHPhotoLibrary.shared().performChanges {
            PHAssetCollectionChangeRequest.deleteAssetCollections(NSArray.init(object: album))
        } completionHandler: { (success, error) in
            DispatchQueue.main.async {
                undoManager?.endUndoGrouping()
                undoManager?.groupsByEvent = true
            }
        }

【问题讨论】:

    标签: swift cocoa objective-c-blocks nsundomanager


    【解决方案1】:

    这是一个复杂的解决方案。它有效,但充其量只是一种创新的技巧。

    基础知识:

    我仅在长时间运行的任务完成后才向 NSUndoManager 注册。然后出现的问题是对称撤消操作也是一个长时间运行的任务,并且在完成后也会注册。 NSUndoManager 看到两个独立的操作,而不是(重新)执行/撤消对。

    黑客 1:

    在操作(初始操作或撤消操作)开始时,我检查 UndoManger 当前是否正在撤消或重做。然后它期望注册反向操作。它期望当前的撤消操作与重做操作配对/平衡。我给它一个虚拟操作:

    if (undoManager.undoing || undoManager.redoing) {
        NSObject *dummy = [[NSObject alloc] init];
    
        [undoManager registerUndoWithTarget:dummy selector:@selector(description) object:nil];
        [undoManager performSelector:@selector(removeAllActionsWithTarget:)
                          withObject:dummy
                          afterDelay:0.0];
    }
    

    然后我从撤消堆栈中删除该操作。撤消堆栈现在处于合理/一致的状态。但是,我无法重做当前正在撤消的操作。

    黑客 2:

    当撤消任务完成时,我不能简单地向撤消管理器注册:在任务开始时已经完成(并清除)了。相反,我注册了一个除了再次注册​​到撤消管理器之外什么都不做的任务。然后让撤消管理器撤消。想法:我假装做原始操作,这样当它被撤消时,我可以注册重做操作。

    if (self.undoing) {
        [[undoManager prepareWithInvocationTarget:[self class]] dummyTaskWithArguments:arguments];
        [undoManager undo];
    }
    else {
        [[undoManager prepareWithInvocationTarget:[self class]] taskWithArguments:arguments];
    }
    
    + (void)dummyTaskWithArguments:(id)arguments
    {
        [[undoManager prepareWithInvocationTarget:[self class]] taskWithArguments:arguments];
    }
    

    【讨论】:

      猜你喜欢
      • 2011-12-25
      • 1970-01-01
      • 2016-05-23
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多