核心数据:禁用特定属性的撤消。推荐的方法不起作用

Posted

技术标签:

【中文标题】核心数据:禁用特定属性的撤消。推荐的方法不起作用【英文标题】:core data: disable undo for specific attributes. Recommended approach not working 【发布时间】:2016-02-11 13:34:06 【问题描述】:

我有一个文本字段和一个复选框,由核心数据支持。对复选框的更改应避免进行任何撤消/重做操作。

推荐的方法(在堆栈溢出时发现)是以下 sn-p。

@IBAction func stateDidChange(sender: NSButton?)

    //disable undo manager
    context.processPendingChanges()
    context.undoManager?.disableUndoRegistration()

   //set value
   let value = Bool(sender!.state == NSOnState)
   <some NSManagedObject>.flag = value

    //enable undo manager
    context.processPendingChanges()
    context.undoManager?.enableUndoRegistration()
 

但这不起作用。当用户

    编辑文本字段, 更新复选框, 并继续编辑文本字段,

然后对复选框的更改包含在撤消操作中。

我也试过

     NSNotificationCenter.defaultCenter().postNotificationName(NSUndoManagerCheckpointNotification, object: self.undoManager)
    self.undoManager?.disableUndoRegistration()
    //do work
    NSNotificationCenter.defaultCenter().postNotificationName(NSUndoManagerCheckpointNotification, object: self.undoManager)
    self.undoManager?.enableUndoRegistration()

我什至在 NSManagedObject 子类中尝试过

    var flag : Bool 
    get 
        self.willAccessValueForKey("flag")
        let text = self.primitiveValueForKey("flag") as! Bool
        self.didAccessValueForKey("flag")
        return text
    
    set 
        let context = self.managedObjectContext!
        context.processPendingChanges()
        context.undoManager?.disableUndoRegistration()

        self.willChangeValueForKey("flag")
        self.setPrimitiveValue(newValue, forKey: "flag")
        self.didChangeValueForKey("flag")

        context.processPendingChanges()
        context.undoManager?.enableUndoRegistration()

    

【问题讨论】:

【参考方案1】:

不是真正的答案,但评论太长了(现在修改为真正的答案)。首先,我看到使用该方法可以很好地阻止一些 coreData 操作出现在撤消中。例如,我在创建新对象和在代码中设置初始状态时使用它。使用这种方法,我允许用户在此之后编辑对象,但这些永远不能撤消到对象的初始默认状态之前。所以从这个意义上说,您的建议似乎是正确的。

不过……

我已经看到报告(但我自己还没有测试过)CoreData 撤消的行为与预期不同。我听说不是记录单个属性更改操作的反向操作,而是维护一堆对象状态。如果为真,这可能与您观察到的行为相符。

考虑一个标签 = A 且复选框 = NO 的对象。在启用撤消的情况下将标签设置为 B。状态现在是 B & NO。这可以回滚到 A & NO。现在将复选框设置为 YES 而不撤消。状态现在是 B&YES。如果现在调用 undo,则所需的状态将是 A & YES,但该状态从未存在过。记录的状态堆栈是

B & YES

B & NO - A & NO

但是,正如我所说,我还没有真正测试过这个。不久前,我使用 XML coreData 存储进行了一些不确定的测试,这似乎表明问题远不止于此。另一方面,我可以想象它可能适用于 SQLite 支持的存储,这取决于 CoreData 如何使用底层 SQL 框架。应该进行测试。

如果这是真的,可以推测它是在逐个对象的基础上实现的,并且可以通过将不可撤消的操作放在一对一的子对象中来规避它。这样主对象状态保持一致,在示例中为标签和对 checkBoxObject 的引用。然后该 checkBoxObject 的内部状态可能无关紧要,因为主对象中的引用是不变的。但这必须经过测试。

更新 除了我最初的答案,我现在花时间测试提出的假设并发现它是正确的。 CoreData 似乎将撤消实现为完整对象状态的 LIFO 堆栈。因此,不可能有选择地撤消一个对象内的某些属性。

我还测试了第二个假设,即这些状态 LIFO 堆栈是按对象的,因此可以通过将不可撤消的属性放在与原始对象 1 对 1 链接的单独对象中来规避这个问题。通过此设置,可以获得所需的行为。

XML 和 SQLite 支持的 CoreData 存储的行为是相同的。

我进一步发现,为了获得所需的行为,修改 coreData 属性的代码应该包装在一个撤消分组中,并且应该在关闭撤消组之前在托管对象上下文中调用 -processPendingChanges

【讨论】:

您认为核心数据具有更改对象列表而不是更改属性的假设也是我的印象。布尔值表示 NSOutlineViewController 的扩展状态。不确定我是否想要/应该为一个标志添加关系。 我明白你的意思。但是在我看来,有些数据是“真实”数据,而您拥有的一些数据实际上是 UI 状态持久性。我可以看到将它们分开的案例。现在只有一个 BOOL 看起来很傻,但后来它也可能扩展到其他状态数据。在我看来,无论如何这在未来可能是一个优势,因为并不一定 UI 状态总是应该与数据相关联。仍然需要测试它是否有效。 我现在也明白你的意思了。我已经在考虑如何处理多个选项卡中相同数据的视图,或者如何保存应用程序运行之间的行选择。旋转 UI 状态可能是一个好方法。 很好,如果您采用这种方法并真正发现将不可撤消的东西分离到子对象中是否有效,那么我很乐意看到您将发现作为自我发布在这里回答。我有兴趣知道(但今天懒得自己测试)

以上是关于核心数据:禁用特定属性的撤消。推荐的方法不起作用的主要内容,如果未能解决你的问题,请参考以下文章

锚标记禁用颜色属性不起作用?

为啥在 Spring Boot 项目中使用 Flyway 时撤消迁移不起作用?

从核心数据中删除记录不起作用?

iPad方向以编程方式不起作用

Ace 编辑器暂停/禁用 UndoManager

Angular:使用指令禁用材质按钮不起作用