読者です 読者をやめる 読者になる 読者になる

RxSwift をやる前にちゃんと Observer パターンを学ぶ

Swift iOS デザインパターン RxSwift

RxSwift やるぞ!と思ったはいいものの、ちゃんと Observer パターンを学んだことがなかったので Swift でゼロから書いてみる。

Observer パターン

観察者が何かを監視していて、その監視対象に更新があった際に観察者に通知が届く仕組み。

Subject が監視対象で、 Observer が観察者。
RxSwift でも PublishSubject とか出てくるし、 Observable とかも Subject に該当するのかな? (まだ RxSwift を理解できてない)

実際に書いてみる

Observer パターン - デザインパターン入門 - IT専科 を参考に Swift にしていく。

1. Observer protocol

観察者のインターフェースの protocol を作る。
id は後述するが、 Observer を削除するときに必要になる key みたいなもの。

protocol Observer {
    var id: String { get }

    // Subject からの通知が届く
    func update(_ string: String)
}

extension Observer {
    func update(_ string: String) {
        print("\(type(of: self)) に届いた新しい値は \(string) です。")
    }
}

2. Subject protocol

observers が mutating なため、 class を継承している。

protocol Subject: class {
    // 観察者リスト
    var observers: [Observer] { get set }

    // 観察者を追加する
    func add(_ observer: Observer)

    // 観察者を削除する
    func remove(_ observer: Observer)

    // 観察者に更新を通知する
    func notify(newString: String)
}

extension Subject {
    func add(_ observer: Observer) {
        observers.append(observer)
    }

    func remove(_ observer: Observer) {
        for (index, registerdObserver) in observers.enumerated() {
            // observer.id が登録済みだった場合削除する
            if registerdObserver.id == observer.id {
                self.observers.remove(at: index)
            }
        }
    }

    func notify(newString: String){
        print("新しい値 \(newString) を observer に通知します。")
        observers.forEach { observer in
            observer.update(newString)
        }
    }
}

3. 具象クラス

Observer は id を UUID で作成して重複しないようにしている。

final class SubjectA: Subject {
    var observers: [Observer] = []
}

final class ObserverA: Observer {
    var id = UUID().uuidString
}

final class ObserverB: Observer {
    var id = UUID().uuidString
}

(本当はシステム的に UUID を id にするようにしたほうがよさそうだけどサボる)

4. 実際に動かす

observerA と observerB が subjectA を監視する。
途中で observerB が監視をやめる。

let subjectA = SubjectA()
let observerA = ObserverA()
let observerB = ObserverB()

subjectA.add(observerA)
subjectA.add(observerB)

subjectA.notify(newString: "New!")
// -> 新しい値 New! を observer に通知します。
// -> ObserverA に届いた新しい値は New! です。
// -> ObserverB に届いた新しい値は New! です。

subjectA.remove(observerB)
subjectA.notify(newString: "New!!!!!!!!!!!!!!!")
// -> 新しい値 New!!!!!!!!!!!!!!! を observer に通知します。
// -> ObserverA に届いた新しい値は New!!!!!!!!!!!!!!! です。

想定通りの動作になった。

全コード

Gist - Swift observer pattern.

感想

実際に書いてみて、 Subject や Observer などの関係性などが理解できてよかった。

とはいえ Observer パターンはそんなに難しくないしゼロから書くほど価値はあまりないしちょっと時間の無駄だった感ある、KVO でやればこんな書かなくて良いしな!!!!!!!