KVO(Key-Value Observing)とは、あるオブジェクトに変更などが発生したことを検知して、別の命令を実行するデザインパターンです。
アプリ開発において、Modelは「通知」でViewやControllerへ変更を伝えることが重要です。まずその点に置いては下記のスライドが素晴らしく参考になります。
iOS/Androidアプリエンジニアが理解すべき「Model」の振る舞い
「通知」におけるKVOのサンプルを作ってみました。アップルの公式ドキュメントを参考にしました。
Apple Document Key-Value Observing
ソースはプロジェクトごとこちらにあります。ダウンロードして、お試しください。
https://github.com/nishiyamaosamu/swift-kvo-example
では解説していきます。
##サンプルアプリの概要
「画面をタッチする」たびに「画面上の日時」が「更新される」
というアプリになります。
ver: Xcode 6.2
##MVCの要素と役割
今回のサンプルでは、MVCは下記のような要素と役割になります。
Model:KvoModel => 「日時が更新される」
View:label (ViewController内) => 「画面上の日時」
Controller: ViewController => 「画面をタッチする」& オブザーバー(変更を検知する)
##KvoModel
class KvoModel : NSObject {
class var sharedInstance: KvoModel {
struct Singleton {
static let instance: KvoModel = KvoModel()
}
return Singleton.instance
}
dynamic var myDate = NSDate()
func updateDate(){
self.myDate = NSDate()
}
}
Modelは長寿命なインスタンスである方が望ましいのでSingletonで実装しています。
KVOを実装するために、NSObjectを継承しましょう。
myDateはdynamicを付加することで、オブザーブ出来るようになるようです。
##ViewController
import UIKit
class ViewController: UIViewController {
let label = UILabel()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
self.label.frame.size = CGSize(width: 220, height: 50)
self.label.center = self.view.center
self.view.addSubview(self.label)
}
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
self.label.text = KvoModel.sharedInstance.myDate.description
KvoModel.sharedInstance.addObserver(self, forKeyPath: "myDate", options: .New, context: nil)
}
override func viewDidDisappear(animated: Bool){
KvoModel.sharedInstance.removeObserver(self, forKeyPath: "myDate")
}
override func touchesEnded(touches: NSSet, withEvent event: UIEvent) {
super.touchesEnded(touches, withEvent: event)
println("touched!")
print("Old myDate -> ")
println(KvoModel.sharedInstance.myDate)
KvoModel.sharedInstance.updateDate()
}
override func observeValueForKeyPath(keyPath: String, ofObject object: AnyObject, change: [NSObject : AnyObject], context: UnsafeMutablePointer<Void>) {
if(keyPath == "myDate"){
print("New myDate -> ")
println(KvoModel.sharedInstance.myDate)
self.label.text = KvoModel.sharedInstance.myDate.description
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
touchesEndedでupdateDateすることで、KvoModelのmyDateの値に変更が発生し、それを検知し、observeValueForKeyPathメソッドが実行されるようになります。
またaddObserverする場合は、必ず監視を削除するメソッドもオブジェクトがなくなるまでに行わないと、メモリリークなどが発生するので気をつけてください。ViewControllerであれば、viewDidDisappearでremoveObserverします。
##最後に
今回のサンプルは簡単な例ですが、本当のアプリではAPIやユーザーアクションなど、あらゆるイベントが不定期に発生します。Observerパターンや、KVOを使って、MVCの各層の(少なくともModelだけでも)依存関係を減らし、通知で連携できるシステムを作りたいですね。