protocol extension でmixin的にObserverパターンを実装してみる



勉強のためにswiftのprotocol extensionでObserverパターンを実装してみました。

- Protocol extension でmixin的に利用できるので、クラス構造への影響を少なく利用できる
- 監視する側を`Observer`, 管理される側を`Observable`というprotocolで定義
- 通知するイベントをprotocolのassociated types で`EventType`として定義
- where節で`Observer`と`Observable`の`EventType`が同じであることを条件として指定

```swift

import Foundation

public protocol Observer: Equatable {
    typealias EventType
    func listen(event: EventType)
}

public protocol Observable {
    typealias ObserverType: Observer
    typealias EventType
    var observers: [ObserverType] { get set}
    mutating func addObserver(observer: ObserverType)
    mutating func removeObserver(observer: ObserverType)
    func notify(event: EventType)
}

extension Observable where EventType == ObserverType.EventType {
    mutating public func addObserver(observer: ObserverType) {
        var os = observers
        os.append(observer)
        observers = os
    }
    mutating public func removeObserver(observer: ObserverType) {
        guard let i = observers.indexOf(observer) else { return }
        observers.removeAtIndex(i)
    }
    public func notify(event: EventType) {
        for o in observers {
            o.listen(event)
        }
    }
}
```


## 使い方
### 定義部分

  - `Observer`をconformする側に`EventType`の指定
  - `Observable`をconformする側に`EventType`の指定に加えて、`Observer`の指定、`observers`のgetter, setterの実装
  - `EventType`が`News`, `Publisher`が`Observable`, `Subscriber`が`Observer`


```swift

public enum News {
    case Morning(String, Publisher)
    case Noon   (String, Publisher)
    case Evening(String, Publisher)
    case Extra  (String, Publisher)
}

public struct Subscriber: Observer, Equatable {
    public typealias Event = News
    public var id:   String
    public var name: String

    public func listen(event: Event) {
        switch event {
        case .Morning(let content, let publisher): read(content: content, publisher: publisher)
        case .Noon   (let content, let publisher): read(content: content, publisher: publisher)
        case .Evening(let content, let publisher): read(content: content, publisher: publisher)
        case .Extra  (let content, let publisher): read(content: content, publisher: publisher)
        }
    }

    public func read(content content: String, var publisher: Publisher) {
        print("\(name) reads morning paper: \(content)\n")
        if content.containsString(name) {
            publisher.removeObserver(self)
            print("\(name) unsubscribed\n")
        }
    }
}

public func ==(lhs: Subscriber, rhs: Subscriber) -> Bool {
    return lhs.id == rhs.id
}



public class Publisher: Observable {
    public var name: String
    public typealias ObserverType = Subscriber
    public typealias EventType    = News
    private var _observers: [ObserverType] = []
    public  var  observers: [ObserverType] {
        get { return _observers }
        set { _observers = newValue }
    }
    public init(name: String) {
        self.name = name
    }
}

```

### 利用部分

```

var sentence_spring = Publisher(name: "Bunshun")

var kiyo     = Subscriber(id: "kiyo"   ,  name: "Kiyo")
var kimutaku = Subscriber(id: "kimura" ,  name: "Kimutaku")
var nakai    = Subscriber(id: "nakai"  ,  name: "Nakai")
var yoshiki  = Subscriber(id: "yoshiki",  name: "yoshiki")


sentence_spring.addObserver(kimutaku)
sentence_spring.addObserver(nakai)
sentence_spring.addObserver(kiyo)
sentence_spring.addObserver(yoshiki)


sentence_spring.notify(.Morning("Becky and GESU NO KIWAMI are in inappropriate relationship!!!", sentence_spring))

sentence_spring.removeObserver(nakai)
sentence_spring.removeObserver(kimutaku)

sentence_spring.notify(.Evening("SMAP will be broken up!?"  , sentence_spring))
sentence_spring.notify(.Extra  ("Kiyo is arrested!!"        , sentence_spring))
sentence_spring.notify(.Extra  ("Sean K is pure japanease!!", sentence_spring))

```

## 感想

- `Observable`conformする際に getter, setterを定義する必要があるところをどうにかしたい。
- protocol extensionとassociated typeの組み合わせは強力なので、もっと小慣れて積極的に使っていきたい。

[ここ](https://gist.github.com/kumabook/56f6a349789e7215d114) にxcode Playgroundで実行できる全体のコードをあげています。


こちらからは以上です。