核心数据:禁用特定属性的撤消。推荐的方法不起作用
core data: disable undo for specific attributes. Recommended approach not working
我有一个文本字段和一个复选框,由核心数据支持。任何 undo/redo 操作都不应更改复选框。
推荐的方法(在堆栈溢出中找到)是以下代码段。
@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()
}
}
不是真正的答案,但评论太长了(编辑现在修改为真正的答案)。首先,我看到使用的方法可以很好地阻止某些 coreData 操作出现在撤消中。例如,我在创建新对象和在代码中设置初始状态时使用它。通过这种方法,我允许用户在此之后对对象进行编辑,但这些操作永远无法恢复到对象的初始默认状态之前。所以从这个意义上说,你的建议似乎是正确的。
然而...
我看到报告(但我自己还没有测试过)CoreData 撤消的行为与预期不同。我听说它不是记录单个 属性 更改操作的反向操作,而是维护一堆对象状态。如果为真,这可能符合您观察到的行为。
考虑一个带有标签 = A 和复选框 = NO 的对象。将标签设置为 B 并启用撤消。状态现在是 B & NO。这可以回滚到 A & NO。现在将复选框设置为 YES 而不撤消。状态现在是 B & YES。如果现在调用撤消,所需状态将是 A & YES,但该状态从未存在过。 recored 上的状态堆栈是
B & YES <- 当前状态
B & NO - A & NO <- 过去状态的后进先出堆栈
然而正如我所说,我还没有真正测试过这个。不久前,我用 XML coreData 存储做了一些不确定的测试,这些测试似乎表明问题不止于此。另一方面,我可以想象,根据 CoreData 如何使用底层 SQL 框架,SQLite 支持的存储可能是正确的。应该测试一下。
如果这是真的,可以推测它是在逐个对象的基础上实现的,并且可能可以通过将不可撤消的操作放在一对一的子对象中来规避它。这样主要对象状态保持一致,在示例中是标签和对 checkBoxObject 的引用。那么该 checkBoxObject 的内部状态可能无关紧要,因为主对象中的引用是不变的。但这必须经过测试。
更新
除了我最初的回答,我现在花时间检验提出的假设并发现它是正确的。 CoreData 似乎将撤消实现为完整对象状态的后进先出堆栈。因此,不可能在一个对象中选择性地撤销某些属性。
我还测试了第二个假设,即这些状态 LIFO 堆栈是按对象排列的,因此可以通过将不可撤销的属性放在与原始对象 1 对 1 链接的单独对象中来规避这个问题。通过此设置,可以获得所需的行为。
XML 和 SQLite 支持的 CoreData 存储的行为是相同的。
我进一步发现,要获得所需的行为,修改 coreData 属性的代码应该包含在撤消分组中,并且应该在撤消组关闭之前在托管对象上下文中调用 -processPendingChanges
。
我有一个文本字段和一个复选框,由核心数据支持。任何 undo/redo 操作都不应更改复选框。
推荐的方法(在堆栈溢出中找到)是以下代码段。
@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()
}
}
不是真正的答案,但评论太长了(编辑现在修改为真正的答案)。首先,我看到使用的方法可以很好地阻止某些 coreData 操作出现在撤消中。例如,我在创建新对象和在代码中设置初始状态时使用它。通过这种方法,我允许用户在此之后对对象进行编辑,但这些操作永远无法恢复到对象的初始默认状态之前。所以从这个意义上说,你的建议似乎是正确的。
然而...
我看到报告(但我自己还没有测试过)CoreData 撤消的行为与预期不同。我听说它不是记录单个 属性 更改操作的反向操作,而是维护一堆对象状态。如果为真,这可能符合您观察到的行为。
考虑一个带有标签 = A 和复选框 = NO 的对象。将标签设置为 B 并启用撤消。状态现在是 B & NO。这可以回滚到 A & NO。现在将复选框设置为 YES 而不撤消。状态现在是 B & YES。如果现在调用撤消,所需状态将是 A & YES,但该状态从未存在过。 recored 上的状态堆栈是
B & YES <- 当前状态
B & NO - A & NO <- 过去状态的后进先出堆栈
然而正如我所说,我还没有真正测试过这个。不久前,我用 XML coreData 存储做了一些不确定的测试,这些测试似乎表明问题不止于此。另一方面,我可以想象,根据 CoreData 如何使用底层 SQL 框架,SQLite 支持的存储可能是正确的。应该测试一下。
如果这是真的,可以推测它是在逐个对象的基础上实现的,并且可能可以通过将不可撤消的操作放在一对一的子对象中来规避它。这样主要对象状态保持一致,在示例中是标签和对 checkBoxObject 的引用。那么该 checkBoxObject 的内部状态可能无关紧要,因为主对象中的引用是不变的。但这必须经过测试。
更新 除了我最初的回答,我现在花时间检验提出的假设并发现它是正确的。 CoreData 似乎将撤消实现为完整对象状态的后进先出堆栈。因此,不可能在一个对象中选择性地撤销某些属性。
我还测试了第二个假设,即这些状态 LIFO 堆栈是按对象排列的,因此可以通过将不可撤销的属性放在与原始对象 1 对 1 链接的单独对象中来规避这个问题。通过此设置,可以获得所需的行为。
XML 和 SQLite 支持的 CoreData 存储的行为是相同的。
我进一步发现,要获得所需的行为,修改 coreData 属性的代码应该包含在撤消分组中,并且应该在撤消组关闭之前在托管对象上下文中调用 -processPendingChanges
。