如何在Swift 4中使用Smart KeyPaths进行键值观察?

Tor*_*ora 8 key-value-observing swift swift4

你能帮我看看如何在NSArrayController使用Smart KeyPaths修改内容时收到通知吗?

灵感来自

键值观察:https: //developer.apple.com/library/content/documentation/Swift/Conceptual/BuildingCocoaApps/AdoptingCocoaDesignPatterns.html#//apple_ref/doc/uid/TP40014216-CH7-ID12

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)")
    }
  }
}
Run Code Online (Sandbox Code Playgroud)

但是,这是行不通的.对目标对象所做的任何更改都不会触发通知.

相比之下,下面列出的典型方法是有效的.

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)
    }
  }
}
Run Code Online (Sandbox Code Playgroud)

新方式看起来更优雅.你有什么建议吗?

环境:Xcode 9 Beta

  • macOS,Cocoa App,Swift 4
  • 创建基于文档的应用程序
  • 使用核心数据

  • myArrayController模式是实体名称,准备好了Document.xcdatamodeld

  • myArrayController托管对象上下文绑定到模型密钥路径:representedObject.managedObjectContext
  • representedObject被赋予了实例Document.
  • NSTableView内容,选择索引排序描述符绑定到的的对应关系myArrayController.

有关环境的更多信息: 绑定managedObjectContext,Xcode 8.3.2,Storyboards,mac:https: //forums.bignerdranch.com/t/binding-managedobjectcontext-xcode-8-3-2-storyboards-macos-swift/12284

编辑:

关于例子情况下上面提到的,我已经改变了我的脑海里来观察managedObjectContext,而不是contentNSArrayController.

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)")
  }

}
Run Code Online (Sandbox Code Playgroud)

原因是第二种方法比第一种方法简单.此代码涵盖了所有所需的要求:表行的添加和删除,以及表单元格值的修改.缺点是每个其他表的修改和应用程序中的每个其他实体的修改都将导致通知.但是,这样的通知并不有趣.但是,这不是什么大问题.

相反,第一种方法需要更多的复杂性.

对于增删,我们需要观察任何contentNSArrayController或实现两个功能

func tableView(_ tableView: NSTableView, didAdd rowView: NSTableRowView, forRow row: Int)
func tableView(_ tableView: NSTableView, didRemove rowView: NSTableRowView, forRow row: Int)
Run Code Online (Sandbox Code Playgroud)

来自NSTableViewDelegate.NSTableViewdelegate是连接到NSViewController.

稍微令人惊讶的是,这两个tableView()功能将被频繁调用.例如,在表中有十行的情况下,排序行将导致十次didRemove调用,然后是十次didAdd调用; 添加一行将导致十次didRemove调用,然后是十一次didAdd调用.那不是那么有效.

对于修改,我们需要

func control(_ control: NSControl, textShouldEndEditing fieldEditor: NSText) -> Bool
Run Code Online (Sandbox Code Playgroud)

NSControlTextEditingDelegate,超级NSTableViewDelegate.每天NSTextField每个表列的应连接到NSViewController通过其delegate.

此外,遗憾的是,这control()是在文本编辑完成后立即调用,而是在NSArrayController更新实际值之前调用.那是有点无用的.我还没有找到第一种方法的好解决方案.

无论如何,这篇文章的主要内容是如何使用Smart KeyPaths.:-)

编辑2:

我要两个都用

  1. 观察一个属性contentNSArrayController......第一个
  2. 观察Notification被发布的NSManagedObjectContext......第二个

1表示用户更改主 - 详细信息视图时,不会进行更改NSManagedObjectContext.

2用于当用户对其进行更改时:添加,删除,更新以及撤消Command-Z,其中没有鼠标事件.

目前,addObserver(self, forKeyPath: "content", ...将使用该版本.一旦这篇文章的问题得到解决,我将切换到observe(\.content, ...

谢谢.

编辑3:

代码2.观察a Notification已经完全被新的代替.

Adi*_*yam 17

至于你的初始代码,这里应该是这样的:

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)
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

observe(...)函数返回一个瞬态观察者,其生命周期表示您将收到通知的时间.如果返回的观察者是deinit'd,您将不再收到通知.在您的情况下,您从未保留对象,因此它在方法范围之后立即死亡.

另外,要手动停止观察,只需设置mySubnil隐藏deinit的旧观察者对象.

  • 对于那些寻找更多信息的人来说,这个不错的[WWDC 2017会话](https://developer.apple.com/videos/play/wwdc2017/212/?time=1214)提供了有关*关键路径观察*的更多详细信息. (3认同)
  • @Yasper:那有什么好处?似乎无用的样板代码;销毁无论如何都会取消观察。 (2认同)