Swift

SwiftでKVOを使って堅牢なMVCを実現するサンプル

More than 3 years have passed since last update.

KVO(Key-Value Observing)とは、あるオブジェクトに変更などが発生したことを検知して、別の命令を実行するデザインパターンです。

アプリ開発において、Modelは「通知」でViewやControllerへ変更を伝えることが重要です。まずその点に置いては下記のスライドが素晴らしく参考になります。

iOS/Androidアプリエンジニアが理解すべき「Model」の振る舞い

「通知」におけるKVOのサンプルを作ってみました。アップルの公式ドキュメントを参考にしました。

Apple Document Key-Value Observing

ソースはプロジェクトごとこちらにあります。ダウンロードして、お試しください。

https://github.com/nishiyamaosamu/swift-kvo-example

では解説していきます。


サンプルアプリの概要

「画面をタッチする」たびに「画面上の日時」が「更新される」

というアプリになります。

image

ver: Xcode 6.2


MVCの要素と役割

今回のサンプルでは、MVCは下記のような要素と役割になります。

Model:KvoModel => 「日時が更新される」

View:label (ViewController内) => 「画面上の日時」

Controller: ViewController => 「画面をタッチする」& オブザーバー(変更を検知する)


KvoModel


KvoModel.swift

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


ViewController.swift

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だけでも)依存関係を減らし、通知で連携できるシステムを作りたいですね。