LoginSignup
12

More than 5 years have passed since last update.

SwiftでKVOを簡単に行う by BTKKeyValueObserver

Last updated at Posted at 2015-06-18

KVOとは

Key Value Observing
説明書くとそれだけで長くなるので割愛。知らない人はいますぐ調べた方がいいレベルで便利です。

KVOの不満

add,removeが超面倒。これに尽きます。さらに、忘れると解放済みメモリにアクセスして落ちるので気を抜けません。

BTKKeyValueObserver

僕が欲しいのは、「ある型をもったオブジェクトの指定したパラメタを監視し、変更があったらフックを実行」という機能です。モデルの更新にあわせてUIを更新するようなケースですね。それを、できる限り簡単、安全に書きたいと思い、BTKKeyValueObserverをつくってみました。
監視対象をセットした時点でも一度フックが呼ばれるので、UI更新が非常に楽になります。

import UIKit

protocol BTKKeyValueObserverDelegate:class{
    func btk_observe(observer:AnyObject,
    valueForKeyPath keyPath:String,
        ofObject object: AnyObject,
        change: [NSObject : AnyObject],
        context: UnsafeMutablePointer<Void>)
}

class BTKKeyValueObserver<T:NSObject> {

    weak var delegate:BTKKeyValueObserverDelegate?

    var target:T?{
        willSet{
            stopObserving()
        }
        didSet{
            startObserving()
        }
    }

    init(keyPaths:[String])
    {
        self.keyPaths = keyPaths
        realObserver.observeValueForKeyPath = {[weak self](keyPath, object, change, context) -> Void in
            if let d = self?.delegate{
                d.btk_observe(self!, valueForKeyPath: keyPath, ofObject: object, change: change, context: context)
            }
        }
    }

    deinit
    {
        stopObserving()
    }

    private var realObserver = BTKKeyValueObserverBridge()
    private var keyPaths:[String]

    func startObserving()
    {
        if let t = target{
            for keyPath in keyPaths{
                t.addObserver(realObserver, forKeyPath: keyPath, options: .New | .Old | .Initial , context: nil)
            }
        }
    }

    func stopObserving()
    {
        if let t = target{
            for keyPath in keyPaths{
                t.removeObserver(realObserver, forKeyPath: keyPath)
            }
        }
    }

}

private class BTKKeyValueObserverBridge: NSObject
{
    var observeValueForKeyPath:((String, AnyObject, [NSObject: AnyObject], UnsafeMutablePointer<Void>) -> Void)?

    override func observeValueForKeyPath(keyPath:String,
        ofObject object: AnyObject,
        change: [NSObject : AnyObject],
        context: UnsafeMutablePointer<Void>)
    {
        if let callback = observeValueForKeyPath{
            callback(keyPath, object, change, context)
        }
    }
}

使い方

監視対象オブジェクトのdidSetフックを使います。あとは、監視対象設定時、および監視パラメータ変更時にdelegateメソッドが呼び出されるので、煮るなり焼くなりしてください。

    lazy var observer:BTKKeyValueObserver<BTKBillObject> = {
        let o = BTKKeyValueObserver<BTKBillObject>(keyPaths: ["paidBy"])
        o.delegate = self
        return o
        }()


    var bill : BTKBillObject?{
        didSet{
            observer.target = bill
        }
    }

    // MARK: BTKKeyValueObserverDelegate

    func btk_observe(observer:AnyObject, valueForKeyPath keyPath: String, ofObject object: AnyObject, change: [NSObject : AnyObject], context: UnsafeMutablePointer<Void>) {
        // do whatever
    }

Pods欲しい人いますか?

あまりにも単純なクラスなのでPodsにしてないのですが、希望者いますか?

オブジェクトが削除された場合の処理

監視対象のオブジェクトがCore Dataによって削除されると落ちてしまうことがわかりました。言うまでもなくARCを使っているのですが、それでもオブジェクトが消えてしまっているような。。。
また、NSManagedObjectのisDeletedフラグはStoreに保存されたオブジェクトに対してしか使えないので、手元ではwillBeDeletedというプロパティを追加することで回避しています。もっといい方法があるといいのですが。。

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
12