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というプロパティを追加することで回避しています。もっといい方法があるといいのですが。。