web-dev-qa-db-ja.com

Swift 4のスマートキーパスでKey-Value監視を使用するにはどうすればよいですか?

Smart KeyPathsを使用して、NSArrayControllerの内容が変更されたときに通知を受け取る方法を教えてください。

に触発された

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

Smart KeyPaths:Swiftのより優れたKey-Valueコーディング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)
    }
  }
}
_

新しい方法はよりエレガントに見えます。あなたの提案はありますか?

環境:Xcode9ベータ

  • macOS、Cocoaアプリ、Swift 4
  • ドキュメントベースのアプリケーションを作成する
  • CoreDataを使用する

  • myArrayControllerのモードはエンティティ名で、_Document.xcdatamodeld_で作成されます

  • myArrayController管理対象オブジェクトコンテキストモデルキーパスにバインドされています:_representedObject.managedObjectContext_
  • representedObjectにはDocumentのインスタンスが割り当てられます。
  • NSTableView'sContentSelection Indexes、およびSort DescriptorsmyArrayControllerの対応にバインドされています。

環境の詳細:BindingmanagedObjectContext、Xcode 8.3.2、Storyboards、machttps://forums.bignerdranch.com/t/binding-managedobjectcontext- xcode-8-3-2-storyboards-macos-Swift/12284

[〜#〜]編集済み[〜#〜]

上記の例のケースに関して、私はmanagedObjectContextcontentではなく、NSArrayControllerを観察するように考えを変えました。

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

}
_

その理由は、この2番目のアプローチが最初のアプローチよりも単純だからです。このコードは、テーブル行の追加と削除、およびテーブルセルの値の変更など、必要なすべての要件をカバーしています。欠点は、アプリ内でテーブルが1つおきに変更され、さらにエンティティが変更されるたびに通知が発生することです。ただし、このような通知は興味深いものではありません。しかし、それは大したことではありません。

対照的に、最初のアプローチはより複雑にする必要があります。

追加と削除の場合、contentNSArrayControllerを監視するか、2つの関数を実装する必要があります。

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

NSTableViewDelegateから。 NSTableViewdelegateNSViewControllerに接続されています。

少し意外なことに、両方のtableView()関数が頻繁に呼び出されます。たとえば、テーブルに10行ある状況では、行を並べ替えると、10回のdidRemove呼び出しとそれに続く10回のdidAdd呼び出しが発生します。 1つの行を追加すると、10回のdidRemove呼び出しが発生し、次に11回のdidAdd呼び出しが発生します。それはそれほど効率的ではありません。

変更するには、

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

NSControlTextEditingDelegateから、NSTableViewDelegateのスーパー。各テーブル列のすべてのNSTextFieldは、そのNSViewControllerを介してdelegateに接続する必要があります。

さらに、残念ながら、このcontrol()は、テキストの編集が完了した直後ではなく、NSArrayControllerの実際の値が更新される前に呼び出されます。つまり、やや役に立たない。私はまだ最初のアプローチで良い解決策を見つけていません。

[〜#〜]とにかく[〜#〜]、この投稿の主なトピックは、スマートキーパスの使用方法です。 :-)

編集済み2

両方を使用します

  1. contentのプロパティNSArrayControllerを観察する...最初のプロパティ
  2. NotificationNSManagedObjectContextによって投稿されているのを観察しています... 2番目のもの

1は、ユーザーがマスター/詳細ビューを変更する場合に使用します。これにより、NSManagedObjectContextは変更されません。

2は、ユーザーが変更を加えた場合に使用します。追加、削除、更新、および元に戻すCommand-Zで、マウスイベントは伴いません。

今のところ、_addObserver(self, forKeyPath: "content", ..._のバージョンが使用されます。この投稿の質問が解決したら、_observe(\.content, ..._のバージョンに切り替えます

ありがとう。

編集済み3

Notificationを監視するコード2.は完全に新しいものに置き換えられました。

8
Tora

最初のコードについては、次のようになります。

_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の場合、通知を受信しなくなります。あなたの場合、オブジェクトを保持したことがないため、メソッドスコープの直後にオブジェクトが停止しました。

さらに、手動で監視を停止するには、mySubnilに設定します。これにより、古いオブザーバーオブジェクトが暗黙的にdeinitsになります。

17
Aditya Vaidyam