如何在 Swift 4 中使用 Key-Value Observing 和 Smart KeyPaths?
How can I use Key-Value Observing with Smart KeyPaths in Swift 4?
你能帮助我如何使用 Smart KeyPaths 在 NSArrayController
的内容被修改时收到通知吗?
灵感来自
Smart KeyPaths:更好的键值编码 Swift:
https://github.com/apple/swift-evolution/blob/master/proposals/0161-key-paths.md
我模仿了文章的示例代码。
class myArrayController: NSArrayController {
required init?(coder: NSCoder) {
super.init(coder: coder)
observe(\.content, options: [.new]) { object, change in
print("Observed a change to \(object.content.debugDescription)")
}
}
}
但是,这是行不通的。对目标对象所做的任何更改都不会触发通知。
相比之下,下面列出的典型方法有效。
class myArrayController: NSArrayController {
required init?(coder: NSCoder) {
super.init(coder: coder)
addObserver(self, forKeyPath: "content", options: .new, context: nil)
}
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
if keyPath == "content" {
print("Observed a change to \((object as! myArrayController).content.debugDescription)")
}
else {
super.observeValue(forKeyPath: keyPath, of: object, change: change, context: context)
}
}
}
新方式看起来更优雅。你有什么建议吗?
环境:Xcode 9 Beta
- macOS, Cocoa 应用, Swift 4
- 创建基于文档的应用程序
使用核心数据
myArrayController
的模式是实体名称,用Document.xcdatamodeld
准备
myArrayController
的 Managed Object Context 绑定到 Model Key Path: representedObject.managedObjectContext
representedObject
分配了 Document
的实例。
NSTableView
的内容、选择索引和排序描述符绑定到 myArrayController
. 的对应关系
有关环境的更多信息:
绑定 managedObjectContext,Xcode 8.3.2,故事板,mac:
https://forums.bignerdranch.com/t/binding-managedobjectcontext-xcode-8-3-2-storyboards-macos-swift/12284
已编辑:
关于上面引用的示例案例,我改变了主意观察 managedObjectContext
,而不是 NSArrayController
的 content
。
class myViewController: NSViewController {
override func viewWillAppear() {
super.viewWillAppear()
let n = NotificationCenter.default
n.addObserver(self, selector: #selector(mocDidChange(notification:)),
name: NSNotification.Name.NSManagedObjectContextObjectsDidChange,
object: (representedObject as! Document).managedObjectContext)
}
}
@objc func mocDidChange(notification n: Notification) {
print("\nmocDidChange():\n\(n)")
}
}
原因是第二种方法比第一种方法简单。此代码涵盖所有所需的要求:添加和删除 table 行,以及修改 table 单元格的值。缺点是应用程序内每另一个 table 的修改和每一个另一个实体的修改都会引起通知。不过,这样的通知并不有趣。不过,这没什么大不了的。
相比之下,第一种方法需要更多的复杂性。
对于增删改查,我们要么观察content
of NSArrayController
,要么实现两个函数
func tableView(_ tableView: NSTableView, didAdd rowView: NSTableRowView, forRow row: Int)
func tableView(_ tableView: NSTableView, didRemove rowView: NSTableRowView, forRow row: Int)
来自 NSTableViewDelegate
。 NSTableView
的 delegate
连接到 NSViewController
。
有点意外的是,tableView()
函数会被如此频繁地调用。例如,在 table 中有十行的情况下,对行进行排序将导致十次 didRemove
调用,然后是十次 didAdd
调用;添加一行将导致十次 didRemove
调用,然后是十一次 didAdd
调用。那不是那么有效。
对于修改,我们需要
func control(_ control: NSControl, textShouldEndEditing fieldEditor: NSText) -> Bool
来自NSControlTextEditingDelegate
,NSTableViewDelegate
的超级。每个 table 列的每个 NSTextField
都应通过其 delegate
.
连接到 NSViewController
此外,不幸的是,此 control()
是在文本编辑完成后立即调用的,而是在 NSArrayController
中的实际值更新之前调用的。也就是说,有点没用。我还没有找到第一种方法的好的解决方案。
无论如何,本post的主要主题是如何使用Smart KeyPaths。 :-)
已编辑 2:
我将同时使用两者
- 正在观察
NSArrayController
的 属性 content
... 第一个
- 观察
Notification
被 NSManagedObjectContext
编辑 post ... 第二个
1 用于当用户更改主从视图时,这不会在 NSManagedObjectContext
上进行更改。
2是当用户对其进行更改时:添加、删除、更新以及撤消,Command-Z,不伴随鼠标事件.
暂时使用addObserver(self, forKeyPath: "content", ...
版本。这个post的问题解决后,我会切换到observe(\.content, ...
的版本
谢谢。
已编辑 3:
代码 2. 观察 Notification
已完全替换为新代码。
至于您的初始代码,它应该如下所示:
class myArrayController: NSArrayController {
private var mySub: Any? = nil
required init?(coder: NSCoder) {
super.init(coder: coder)
self.mySub = self.observe(\.content, options: [.new]) { object, change in
debugPrint("Observed a change to", object.content)
}
}
}
observe(...)
函数 returns 一个短暂的观察者,其生命周期指示您将在多长时间内收到通知。如果返回的观察者是 deinit
',您将不再收到通知。在您的情况下,您从未保留该对象,因此它在方法作用域之后就消失了。
此外,要手动停止观察,只需将 mySub
设置为 nil
,这将隐式 deinit
旧的观察者对象。
你能帮助我如何使用 Smart KeyPaths 在 NSArrayController
的内容被修改时收到通知吗?
灵感来自
Smart KeyPaths:更好的键值编码 Swift: https://github.com/apple/swift-evolution/blob/master/proposals/0161-key-paths.md
我模仿了文章的示例代码。
class myArrayController: NSArrayController {
required init?(coder: NSCoder) {
super.init(coder: coder)
observe(\.content, options: [.new]) { object, change in
print("Observed a change to \(object.content.debugDescription)")
}
}
}
但是,这是行不通的。对目标对象所做的任何更改都不会触发通知。
相比之下,下面列出的典型方法有效。
class myArrayController: NSArrayController {
required init?(coder: NSCoder) {
super.init(coder: coder)
addObserver(self, forKeyPath: "content", options: .new, context: nil)
}
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
if keyPath == "content" {
print("Observed a change to \((object as! myArrayController).content.debugDescription)")
}
else {
super.observeValue(forKeyPath: keyPath, of: object, change: change, context: context)
}
}
}
新方式看起来更优雅。你有什么建议吗?
环境:Xcode 9 Beta
- macOS, Cocoa 应用, Swift 4
- 创建基于文档的应用程序
使用核心数据
myArrayController
的模式是实体名称,用Document.xcdatamodeld
准备
myArrayController
的 Managed Object Context 绑定到 Model Key Path:representedObject.managedObjectContext
representedObject
分配了Document
的实例。NSTableView
的内容、选择索引和排序描述符绑定到myArrayController
. 的对应关系
有关环境的更多信息: 绑定 managedObjectContext,Xcode 8.3.2,故事板,mac: https://forums.bignerdranch.com/t/binding-managedobjectcontext-xcode-8-3-2-storyboards-macos-swift/12284
已编辑:
关于上面引用的示例案例,我改变了主意观察 managedObjectContext
,而不是 NSArrayController
的 content
。
class myViewController: NSViewController {
override func viewWillAppear() {
super.viewWillAppear()
let n = NotificationCenter.default
n.addObserver(self, selector: #selector(mocDidChange(notification:)),
name: NSNotification.Name.NSManagedObjectContextObjectsDidChange,
object: (representedObject as! Document).managedObjectContext)
}
}
@objc func mocDidChange(notification n: Notification) {
print("\nmocDidChange():\n\(n)")
}
}
原因是第二种方法比第一种方法简单。此代码涵盖所有所需的要求:添加和删除 table 行,以及修改 table 单元格的值。缺点是应用程序内每另一个 table 的修改和每一个另一个实体的修改都会引起通知。不过,这样的通知并不有趣。不过,这没什么大不了的。
相比之下,第一种方法需要更多的复杂性。
对于增删改查,我们要么观察content
of NSArrayController
,要么实现两个函数
func tableView(_ tableView: NSTableView, didAdd rowView: NSTableRowView, forRow row: Int)
func tableView(_ tableView: NSTableView, didRemove rowView: NSTableRowView, forRow row: Int)
来自 NSTableViewDelegate
。 NSTableView
的 delegate
连接到 NSViewController
。
有点意外的是,tableView()
函数会被如此频繁地调用。例如,在 table 中有十行的情况下,对行进行排序将导致十次 didRemove
调用,然后是十次 didAdd
调用;添加一行将导致十次 didRemove
调用,然后是十一次 didAdd
调用。那不是那么有效。
对于修改,我们需要
func control(_ control: NSControl, textShouldEndEditing fieldEditor: NSText) -> Bool
来自NSControlTextEditingDelegate
,NSTableViewDelegate
的超级。每个 table 列的每个 NSTextField
都应通过其 delegate
.
NSViewController
此外,不幸的是,此 control()
是在文本编辑完成后立即调用的,而是在 NSArrayController
中的实际值更新之前调用的。也就是说,有点没用。我还没有找到第一种方法的好的解决方案。
无论如何,本post的主要主题是如何使用Smart KeyPaths。 :-)
已编辑 2:
我将同时使用两者
- 正在观察
NSArrayController
的 属性content
... 第一个 - 观察
Notification
被NSManagedObjectContext
编辑 post ... 第二个
1 用于当用户更改主从视图时,这不会在 NSManagedObjectContext
上进行更改。
2是当用户对其进行更改时:添加、删除、更新以及撤消,Command-Z,不伴随鼠标事件.
暂时使用addObserver(self, forKeyPath: "content", ...
版本。这个post的问题解决后,我会切换到observe(\.content, ...
谢谢。
已编辑 3:
代码 2. 观察 Notification
已完全替换为新代码。
至于您的初始代码,它应该如下所示:
class myArrayController: NSArrayController {
private var mySub: Any? = nil
required init?(coder: NSCoder) {
super.init(coder: coder)
self.mySub = self.observe(\.content, options: [.new]) { object, change in
debugPrint("Observed a change to", object.content)
}
}
}
observe(...)
函数 returns 一个短暂的观察者,其生命周期指示您将在多长时间内收到通知。如果返回的观察者是 deinit
',您将不再收到通知。在您的情况下,您从未保留该对象,因此它在方法作用域之后就消失了。
此外,要手动停止观察,只需将 mySub
设置为 nil
,这将隐式 deinit
旧的观察者对象。