2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

KVO(Key Value Observe) with Foundation in Swift

Last updated at Posted at 2020-03-25

KVOとは

  • Key Value Observeの略
  • 「キー値監視」と訳されている (Swiftのプロパティ監視とは別)
  • オブジェクトの属性値の監視をプロパティを通してではなく、キー(文字列、#keyPath式)を使って行うことができる

SwiftでKVOを行うには

  • 通知を受け取る方も通知する方もNSObjectを継承する

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)

参考

2
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?