テックノート

Swiftでキーバリューオブザーバーを使う

他のオブジェクトのプロパティの変更をオブジェクトに通知する。
代表者 ロジャース リチャード 投稿者: ロジャース リチャード
xCode 6+ iOS 8+ OSX v10.10+ Swift

Key-Value Observing(略して KVO)は、Cocoa API の重要なコンセプトです。これにより、他のオブジェクトの状態が変化したときに、オブジェクトに通知することができます。とても便利そうですね。そうでしょう?

概要

キーバリュー観察は、Cocoaのプログラミングパターンの一つで、他のオブジェクトのプロパティの変更をオブジェクトに通知するために使用します。モデルとビューのように、アプリの論理的に分離された部分の間で変更を伝達するのに便利です。キーバリューオブザベーションは、NSObjectを継承したクラスでのみ使用できます。

プロパティにアノテーションを付けて、キー・バリューを観察する

key-value observingで観測したいプロパティを@objc属性とdynamic修飾子の両方でマークします。以下の例では、MyObjectToObserveクラスに、観測可能なプロパティ-MyDate-を定義しています。

class MyObjectToObserve: NSObject {
    @objc dynamic var myDate = NSDate(timeIntervalSince1970: 0) // 1970
    func updateDate() {
        myDate = myDate.addingTimeInterval(Double(2 << 30)) // 約68年分を追加
    }
}

オブザーバーの定義

オブザーバークラスのインスタンスは、1つまたは複数のプロパティに加えられた変更に関する情報を管理します。オブザーバを作成する際には、観察したいプロパティを参照するキーパスを指定してobserve(_:options:changeHandler:)メソッドを呼び出し、観察を開始します。

下記の例では、MyObjectToObserveのmyDateプロパティを参照しています。

class MyObserver: NSObject {
    @objc var objectToObserve: MyObjectToObserve
    var observation: NSKeyValueObservation?

    init(object: MyObjectToObserve) {
        objectToObserve = object
        super.init()

        observation = observe(
            \.objectToObserve.myDate,
            options: [.old, .new]
        ) { object, change in
            print("myDate changed from: \(change.oldValue!), updated to: \(change.newValue!)")
        }
    }
}

NSKeyValueObservedChangeインスタンスのoldValueとnewValueプロパティを使って、観察しているプロパティの変更点を確認します。

プロパティがどのように変化したかを知る必要がない場合は、options パラメータを省略します。optionsパラメータを省略すると、新旧のプロパティ値を保存しないため、oldValueプロパティとnewValueプロパティがnilになります。

観察者と観察する性質の関連付け

観測したいプロパティとそのオブザーバーを関連付けるには、オブザーバーのイニシャライザーにオブジェクトを渡します。

let observed = MyObjectToObserve()
let observer = MyObserver(object: observed)

プロパティー変更への対応

上記のように、キー・バリュー・オブザベーションを使用するように設定されているオブジェクトは、プロパティの変更をオブザーバーに通知します。以下の例では、updateメソッドを呼び出してmyDateプロパティを変更しています。このメソッドの呼び出しにより、オブザーバーの変更ハンドラが自動的に起動します。

observed.updateDate() // オブザーバーの変更ハンドラをトリガします。
// Prints "myDate changed from: 1970-01-01 00:00:00 +0000, updated to: 2038-01-19 03:14:08 +0000"

上の例では、日付の新旧両方の値を表示することで、プロパティの変更に対応しています。