首页 文章

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

提问于
浏览
8

你能帮我看看如何修改 NSArrayController 的内容,并使用 Smart KeyPaths 进行通知吗?

灵感来自

Key-Value Observinghttps://developer.apple.com/library/content/documentation/Swift/Conceptual/BuildingCocoaApps/AdoptingCocoaDesignPatterns.html#//apple_ref/doc/uid/TP40014216-CH7-ID12

Smart KeyPaths: Better Key-Value Coding for Swifthttps://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 App,Swift 4

  • 创建基于文档的应用程序

  • 使用核心数据

  • myArrayController 的模式是 Entity Name ,用 Document.xcdatamodeld 编写

  • myArrayControllerManaged Object Context 绑定到 Model Key PathrepresentedObject.managedObjectContext

  • representedObject 分配了 Document 的实例 .

  • NSTableViewContentSelection IndexesSort Descriptors 绑定到 myArrayController 的对应关系 .

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

EDITED

关于上面引用的示例案例,我已经改变主意去观察 managedObjectContext ,而不是 NSArrayControllerNSArrayController .

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

}

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

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

对于添加和删除,我们需要观察 contentNSArrayController 或实现两个函数

func tableView(_ tableView: NSTableView, didAdd rowView: NSTableRowView, forRow row: Int)
func tableView(_ tableView: NSTableView, didRemove rowView: NSTableRowView, forRow row: Int)

来自 NSTableViewDelegate . NSTableViewdelegate 已连接到 NSViewController .

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

对于修改,我们需要

func control(_ control: NSControl, textShouldEndEditing fieldEditor: NSText) -> Bool

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

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

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

EDITED 2

我要两个都用

  • 观察 NSArrayController 的属性 content ...第一个

  • 观察由_579423发布的 Notification ...第二个

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

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

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

谢谢 .

EDITED 3

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

1 回答

  • 14

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

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

    另外,要手动停止观察,只需将 mySub 设置为 nil ,这隐含地是旧观察者对象的 deinit .

相关问题