KVO
#KVC
概要
KVO は、簡潔に言うと、あるオブジェクトが自身のプロパティの変更を他のオブジェクトに通知するための仕組みを提供するメカニズムである。典型的な Observerパターン のような仕組みだけれど、追加の実装を必要とせずに簡単にプロパティの監視ができる。
Notification とは異なり、通知を仲介するオブジェクトは存在せず、オブジェクト間で直接通知が行われる。このため、Notification の場合は監視対象が増えるとコードが肥大化しがち、と言う問題を避けられる。この通知のための仕組みは NSObject に実装済みであるため、NSObject を継承したオブジェクトであれば簡単に KVO の恩恵を受けることができる。
Introduction to Key-Value Observing Programming Guide
Swift で KVO を利用したい場合、以下を満たしている必要がある。
NSObject を継承している
監視対象のプロパティに @objc attribute が付与されている
監視対象のプロパティに dynamic attribute が付与されている
code:swift
class MyObject: NSObject {
@objc dynamic var observableProperty: NSDate
// ...
}
Using Key-Value Observing in Swift | Apple Developer
KVOのライフサイクル
基本的には、以下の手順でプロパティの監視を開始する。
監視対象のオブジェクトに対して、addObserver(_:forKeyPath:options:context:) メソッドで Observer を登録する
登録しても、Observer は監視対象オブジェクトから強参照されないので注意
Observer に observeValueForKeyPath:ofObject:change:context: を実装し、通知を受け取れるようにしておく
監視が不要になったタイミングで removeObserver(_:forKeyPath:) で登録を解除する
Observer が解放される前に解除しておく必要がある
監視の開始
KVO で値を監視するには、まず addObserver で監視を開始する必要がある。
code:swift
func addObserver(_ observer: NSObject,
forKeyPath keyPath: String,
options: NSKeyValueObservingOptions = [],
context: UnsafeMutableRawPointer?)
https://developer.apple.com/documentation/objectivec/nsobject/1412787-addobserver
option には、どのような値を通知に含めるか?を指定する。通知の内容は observeValueForKeyPath:ofObject:change:context: の change パラメータとして Dictionary の形で受け取ることができる。この Dictionary には NSKeyValueChangeKey がキーとして指定される。
変更前の値は .old, 変更後の値は .new を指定すると、各々キー newKey, oldKey に値が格納されて返ってくる。変更前後の両方の値が欲しい場合は .old | .new のように OR でつなぐ。監視後に初期値を通知させたい場合は .initial、プロパティが実際に変更される前後で通知が欲しい場合は .prior を指定する。
https://developer.apple.com/documentation/foundation/nskeyvalueobservingoptions
通知の受け取り
change Dictionary には、指定した option に対応した値以外にも情報が格納されている。
NSKeyValueChangeKey.kindKey に格納されている値は NSKeyValueChange という enum 値であり、監視対象のプロパティの変化の種類を確認できる。単純に監視対象のプロパティの値が変更された場合は .setting が格納される。監視対象のプロパティが複数の Observer に監視されていた場合、それらの追加/削除/入れ替えに伴って.insertion, .removal, .replacement が格納される。
KVOにおける値が「変更された」とは?
KVO におけるプロパティ監視では、値の「変更」とは、そのプロパティの setter が呼び出されたことを示す。そのため、設定の前後でその内容に変化がなくとも通知は行われる ので、注意。
https://stackoverflow.com/a/14448182
KVO Compatible
WIP
https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/KeyValueObserving/Articles/KVOCompliance.html#//apple_ref/doc/uid/20002178-BAJEAIEE
KVC (Key Value Coding)
オブジェクトのプロパティに間接的にアクセスさせる
プロパティへ間接的にアクセスできる
プロパティのアクセサや、変数自体に直接アクセスさせない
NSKeyValueCoding を実装しているオブジェクトに対しては利用できる
swift では、コンパイルやランタイムの仕様、アクセス制御等のためにさまざまなキーワードや Attribute をサポートしている
例えば、final を増やしたクラスはサブクラスが作れない
これにより、コンパイラは特別な最適化が行えるようになる
code:swift
import Foundation
class User: NSObject {
@objc var name: String
@objc var age: Int
@objc var parent: User?
override init () {
self.name = ""
self.age = 0
super.init()
}
}
let user = User()
// 直接設定
user.name = "tasuwo"
user.age = 10
// KVC
user.setValue("tarou", forKey: "name")
user.setValue(100, forKey: "age")
// KVC: KeyPath による設定
user.parent = User()
user.setValue(10, forKeyPath: "parent.age")
user.setValue("test", forKeyPath: "parent.name")
// KVC: #KeyPath でパスを定義
user.setValue(10, forKey: #KeyPath(User.parent))
Swift 4 からは、Objective-C Runtime のコードはパフォーマンスの理由から推論されない。そのため、@objc Attribute をプロパティに追加する必要がある。
#keyPath ディレクティブを利用すると、KeyPath を コンパイル時に チェックしてくれる
Swift 4 以上からは、NSObject を必要としない KVC がある
code:swift
class Student {
var name: String = “”
var gradeLevel: Int = 0
}
let student1 = Student()
student1keyPath: \Student.name = “tasuwo”
KVO (Key Value Observing)
オブジェクトのプロパティに間接的にアクセスさせる方法
プロパティのアクセさや、変数自体に直接アクセスさせない
これに対応させるためには、クラスが NSKeyValueCoding プロトコルを実装している必要がある
KVC (Key Value Coding)
https://stackoverflow.com/questions/52635302/how-to-learn-about-kvc-with-swift-4-2
KVO & KVC In swift
Key-Value Coding Programming Guide
https://qiita.com/_tid_/items/6d44d0e91d2d02d0cfaa