KVOとは
- Key Value Observeの略
- 「キー値監視」と訳されている (Swiftのプロパティ監視とは別)
- オブジェクトの属性値の監視をプロパティを通してではなく、キー(文字列、#keyPath式)を使って行うことができる
SwiftでKVOを行うには
- 通知を受け取る方も通知する方もNSObjectを継承する
- NSKeyValueObserving プロトコルが自動的に適合される
NSKeyValueObserving
以下の機能が用意されている
- 監視の登録
addObserver(_:forKeyPath:options:context:)
- 変更の受信
-
observeValue(forKeyPath:of:change:context:)
メソッドをオーバーライドする
-
- 変更の通知
- Objective-Cでは、監視キーの値の変更は自動的に通知されるが、Swiftではされない。
- 以下のメソッドがワンセットとなっている
-
willChangeValue(forKey:)
変更前に呼び出す -
didChangeValue(forKey:)
変更後に呼び出す
-
KVOプログラミング例
kvo_sample.swift
class Account: NSObject {
@objc var balance: Double {
// KVOによる値の変更通知
willSet {
self.willChangeValue(forKey: #keyPath(Account.balance))
}
didSet {
self.didChangeValue(forKey: #keyPath(Account.balance))
}
}
@objc var interestRate: Double
init(_ balance: Double, _ interestRate: Double) {
self.balance = balance
self.interestRate = interestRate
}
}
class Person: NSObject {
// 変更通知の登録
func registerAccount(_ account: Account) {
account.addObserver(self, forKeyPath: #keyPath(Account.balance), options: [.new, .old], context: nil)
account.addObserver(self, forKeyPath: #keyPath(Account.interestRate), options: [.new, .old], context: nil)
}
override func observeValue(forKeyPath keyPath: String?,
of object: Any?,
change: [NSKeyValueChangeKey : Any]?,
context: UnsafeMutableRawPointer?) {
// 変更通知を受け取る
}
}
let account = Account(0, 0)
let person = Person()
person.registerAccount(account)
account.balance = 100
KVCによる値の変更通知
KVOで用意されているメソッド(willChange,didChange)ではなく、KVCで値を変更すると、直接変更を通知してくれる。
注意
KVOで通知が実装されている状態でKVCによる値の変更を行うと2回変更通知が飛ぶので注意。
上記を回避したい場合は、以下のメソッドをオーバーライドしてfalse
を返すにようにする。
notify_with_kvc.swift
// 自動的に通知するかどうか
override class func automaticallyNotifiesObservers(forKey key: String) -> Bool {
return false
}
notify_with_kvc_sample.swift
class Account: NSObject {
@objc var balance: Double {
willSet {
self.willChangeValue(forKey: #keyPath(Account.balance))
}
didSet {
self.didChangeValue(forKey: #keyPath(Account.balance))
}
}
@objc var interestRate: Double
init(_ balance: Double, _ interestRate: Double) {
self.balance = balance
self.interestRate = interestRate
}
override class func automaticallyNotifiesObservers(forKey key: String) -> Bool {
// 自動的には通知しないようにする
return false
}
}
class Person: NSObject {
func registerAccount(_ account: Account) {
account.addObserver(self, forKeyPath: #keyPath(Account.balance), options: [.new, .old], context: nil)
account.addObserver(self, forKeyPath: #keyPath(Account.interestRate), options: [.new, .old], context: nil)
}
override func observeValue(forKeyPath keyPath: String?,
of object: Any?,
change: [NSKeyValueChangeKey : Any]?,
context: UnsafeMutableRawPointer?) {
}
}
let account = Account(0, 0)
let person = Person()
person.registerAccount(account)
// KVCによる変更通知
account.setValue(100.0, forKey: #keyPath(Account.balance))
NOTE:
- Objective-Cでは
automaticallyNotifiesObserversForKey:
でfalse
を返した場合、手動で実装する必要がある(willChange, didChange)