[Swift4] KVO(Key-Value Observing)による変数の変化の監視

2016年10月17日(更新: 2018年7月5日)

Swift には Objective-C から引き継いだKVO(Key-Value Observing)という仕組みがあります。

これは、指定した変数(プロパティ)に変化があったとき、それを感知してリアルタイムに処理を行うときに便利な変数監視の機能です。

他のクラスのプロパティに変化があったか確認しようとした場合、通常、そのプロパティを public にしたり get メソッドなどを用意したりして、ある処理を行う都度、もしくは一定時間間隔などにそれらを呼び出すことになります。

しかし、いつ値が変化するか予測できないプロパティを監視しようとした場合、これでは値の更新を見逃してしまう危険があります。

KVOを利用すれば、このような心配が無い上に、値の変化を監視するための処理を自分で作らなくてよいという利点があります。複数のプロパティを監視したり、逆に1つのプロパティの変化を複数のオブジェクトから監視したりもできます。

KVOの簡単なサンプルコード

自クラス内のプロパティの値変化を監視するサンプルコードです。

class ViewController: UIViewController {
    
    @objc dynamic var test: Int = 0 // 監視対象のプロパティ

    override func viewDidLoad() {
        super.viewDidLoad()
        
        // 自身の変数 test を監視対象として登録
        self.addObserver(self, forKeyPath: "test", options: [.old, .new], context: nil)
        
        // 値を変えてみる
        test = 15
        
        // また値を変えてみる
        test = 23
    }
    
    // 監視対象の値に変化があった時に呼ばれる
    override func observeValue(forKeyPath keyPath: String?, of object: Any?, 
        change: [NSKeyValueChangeKey: Any]?, context: UnsafeMutableRawPointer?) {
        
        print(keyPath!) // プロパティ名
        print(object!) // 対象オブジェクト
        print(change![.oldKey]!) // 変化前の値
        print(change![.newKey]!) // 変化後の値
    }
    
    // オブジェクト破棄時に監視を解除
    deinit {
        self.removeObserver(self, forKeyPath: "test")
    }
    
    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
    }
}

実行結果

監視対象の変数の値を 0 から 15、15 から 23 に変えた時の結果です。

変化した変数名、それを持つオブジェクトの情報、そしてプロパティの変化前の値と変化後の値がログに出力されています。

KVOによる値監視のサンプルプログラム

サンプルコードの解説

監視対象の登録

変化を監視するオブジェクトや監視対象の変数を指定するには、以下のメソッドを使います。

func addObserver(_ observer: NSObject, forKeyPath keyPath: String, 
    options: NSKeyValueObservingOptions = [], context: UnsafeMutableRawPointer?)

observer に監視を行うオブジェクトを、keyPath には監視する変数名を文字列で渡します。

options で値の監視方法を設定します。今回の例であれば、値が変化する前の値も得るために .old を設定しています。変化後の値を知りたいだけであれば .new を設定するだけでOKです。

プロパティ変化を感知して呼ばれるメソッド

監視を行う側では、次のメソッドを必ず実装します。監視対象のプロパティに変化があれば、このメソッドが自動的に呼ばれ、変化の詳細を受け取ることができます。

func observeValue(forKeyPath keyPath: String?, of object: Any?, 
    change: [NSKeyValueChangeKey: Any]?, context: UnsafeMutableRawPointer?)

上の例では変化したプロパティ名とそれを持つオブジェクトの名前、そして値がどう変化したかをログに出力しています。

KVOを解除

オブジェクトが破棄されるときは、以下のメソッドを deinit で呼び出してKVOを解除します。

func removeObserver(_ observer: NSObject, forKeyPath keyPath: String)

監視できるプロパティについて

監視対象のプロパティの宣言には dynamic を指定する必要があります。また Swift4 以降では @objc も付けます。

@objc dynamic var test: Int = 0

別のクラスの値を監視するサンプル

他のクラス(Test)のオブジェクトの変数変化を監視するサンプルコードです。

import UIKit

class ViewController: UIViewController {

    let otherObject = Test()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        // 他のオブジェクトの変数を監視対象に登録
        otherObject.addObserver(self, forKeyPath: "observed", options: [.old, .new], context: nil)
        
        // 値を変えてみる
        otherObject.observed = 200
        
        // また値を変えてみる
        otherObject.observed = 555
    }
    
    // 監視対象の値に変化があった時に呼ばれる
    override func observeValue(forKeyPath keyPath: String?, of object: Any?, 
        change: [NSKeyValueChangeKey: Any]?, context: UnsafeMutableRawPointer?) {
        
        print(keyPath!) // プロパティ名
        print(object!) // 対象オブジェクト
        print(change![.oldKey]) // 変化前の値
        print(change![.newKey]) // 変化後の値
    }
    
    // オブジェクト破棄時に監視を解除
    deinit {
        otherObject.removeObserver(self, forKeyPath: "observed")
    }
    
    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
    }
}

class Test: NSObject {
    @objc dynamic var observed: Int = 0
}

実行結果

他のクラスのプロパティを監視するKVO

以上がKVOによる変数の監視の基本です。詳細は以下のドキュメントを御覧ください。

Introduction to Key-Value Observing Programming Guide

[Swift4] KVO(Key-Value Observing)による変数の変化の監視」への1件のフィードバック

  1. ピンバック: Automatically loop video using Notification and KVO | Don't Think Twice It's All Right

コメントを残す

メールアドレスが公開されることはありません。