LoginSignup
190
189

More than 5 years have passed since last update.

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

Last updated at Posted at 2015-03-17

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

190
189
1

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
190
189