勉強のためにswiftのprotocol extensionでObserverパターンを実装してみました。
- Protocol extension でmixin的に利用できるので、クラス構造への影響を少なく利用できる
- 監視する側を
Observer
, 管理される側をObservable
というprotocolで定義 - 通知するイベントをprotocolのassociated types で
EventType
として定義 - where節で
Observer
とObservable
のEventType
が同じであることを条件として指定
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
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の組み合わせは強力なので、もっと小慣れて積極的に使っていきたい。
ここ にxcode Playgroundで実行できる全体のコードをあげています。
こちらからは以上です。