KVOを利用する(Swift)
Jul 25, 2016 · iosswift3
Swift
でKVO
を利用する方法について。特にcontext
を一意の識別子として使いたい場合の方法
サンプルコード
ポイント
NSObject
を継承する
監視対象も監視するクラスも両方ともNSObject
のサブクラスであることが必要
監視対象(サンプルではTarget
)で継承しなかった場合、
'NSUnknownKeyException', reason: '[<〜.ViewController 0x〜> addObserver:<〜.ViewController 0x〜> forKeyPath:@"target.valueA" options:3 context:0x〜] was sent to an object that is not KVC-compliant for the "target" property.'
といった実行時エラーが発生する
監視する側だとそもそもaddObserver
などが利用できない
プロパティにはdynamic
をつける
監視対象のプロパティ(addObserver
で追加するプロパティ)は必ずdynamic
をつけること
もし、これをつけ忘れると、エラーにはならないが、通知も来ない状態
(=変更されてもobserveValueForKeyPath
が呼ばれない)
という判りにくいバグになってしまう
アクセスコントロールに注意
プロパティが別クラスのオブジェクト?の場合、private
にすると
'NSUnknownKeyException', reason: '[<〜.ViewController 0x〜> addObserver:<〜.ViewController 0x〜> forKeyPath:@"target.valueA" options:3 context:0x〜] was sent to an object that is not KVC-compliant for the "target" property.'
といった実行時エラーが発生する
サンプルだと、value1
とvalue2
はprivate
でも問題無いが、target
はprivate
ではエラーになる
String
やInt
ではエラーにならないのは確認したが、具体的な条件は未調査・・・
識別子としてのcontext
の指定
通常の指定方法は参考リンクの通り(private var myContext = 0
)。
ただ、今回のサンプルでは、キー値の指定とまとめて以下のようにしている
private struct KeyContext {
static var value1 = "value1"
...
}
というのも、context
には一意なアドレスを渡すべきなので、static
によりアドレスを確保している
(通常の指定方法ではグローバル変数にして一意なアドレスを確保)
なお、private
なのは単に他からアクセスさせないようにしたい(する必要がない)からで、
struct
の中で宣言しているのは、名前空間のようにしたかったからである。
よって、
private var value1 = "value1"
...
class ViewController: UIViewController {
と言った書き方でも同じ
通知の登録 / 解除
呼び出しタイミング
登録時
サンプルではUIViewController
なので、viewWillAppear
で登録しているが、
通常はinit
での登録が良さげ
解除時
サンプルでは登録がviewWillAppear
なので、対となるviewWillDisappear
で解除しているが、
通常はdeinit
での登録が良い
登録方法
addObserver(self, forKeyPath: KeyContext.value1, options: .new, context: &KeyContext.value1)
Objective-C
との相違点は、
KeyPath
にself
がいらないoptions
を複数指定する時は、|
ではなく配列(例:[.new, .old]
)で渡すcontext
へのポインタは&
だけで良い(暗黙的変換が入る)
といったあたり
通知の受信
今回は、context
を識別子として利用しているので、switch
でまとめて比較しているが、defalut
の時にちゃんと
super.observeValueForKeyPath(keyPath, ofObject: object, change: change, context: context)
を呼ぶこと。これがないと、もし親クラスで何か監視をしていた場合に処理が正しく行われないので (当然、自身の監視対象だった場合は呼ばない)
なお、case
に監視対象のプロパティを書き忘れると、
An -observeValueForKeyPath:ofObject:change:context: message was received but not handled.
の実行時エラーとなる
値の取得
// Change Dictionary Keys:
// NSKeyValueChangeKey.newKeyとかNSKeyValueChangeKey.oldKeyとか
let value = change?["Change Dictionary Keys"] as? "データ型"
と書けば希望のデータ型へ変換して取得できる。
NSNull
や型が違う場合などは、最終的にnil
が入るのでサンプルのようにguard
ではじくのがスマート
Swift2からの変更点
addObserver
のcontext
のポインタがUnsafeMutableRawPointer
に変わっている
参考リンク
開発環境
- Xcode 8.2.1