はじめに
Swiftで普通にObserverパターンを作ろうとすると意外に面倒じゃないでしょうか。
KVOは何かやりたいことと違うし、NSNotificationCenterはTypeSafeじゃないし、通知名の文字列を管理するのも少し面倒です。
その点AndroidのEventBusは少し動的な要素が入りますが(Methodが使われていないとWarningが出るのを抑えないといけない)、なかなかバランスが良いと感じていて好きです。
Swiftでも同じ感じで作れないかなぁと試行錯誤していたら、まあまあ良い感じで作れたのでご紹介します。
SwiftEventBusという名前のpodが既にあったので、 EventCenter と名づけました。
EventCenterの良い所
- 任意のObject(class/struct/enum等)をEventオブジェクトとしてpostできる
- Event通知をもらう側がCastしなくても良い
- Eventの種別は「class/struct/enum 等の型」で決まるので、Event名の文字列を管理しなくて良い(うっかり重複することがない)
- どのThreadでEvent通知を欲しいかは、Event通知をもらう側で決められる
制限
- swift1.2で動作確認しています
使い方
Install
cocoapods
pod "EventCenter"
マニュアル Install
実体は1ファイルなので、
https://github.com/mokemokechicken/EventCenter/blob/master/Pod/Classes/EventCenter.swift
をそのままコピっても良いかもしれません。
使いかた
例えば、こんな感じです。
class ViewController: UIViewController {
override func viewWillAppear(animated: Bool) {
let ec = EventCenter.defaultCenter
// Event HandlerとしてClosureを登録する。この引数の型にマッチしたHandlerのみが呼ばれる
ec.register(self) { (event: MyAwesomeModel.UpdateEvent) in
self.updateView()
}
ec.register(self) { (event: MyAwesomeModel.StoreEvent) in
switch(event) {
case .SUCCESS:
print("store ok!")
case .ERROR:
print("store error!")
}
}
// または、methodを登録することもできます。型がマッチしたEventの場合呼ばれます。
ec.register(self, handler: self.onEvent)
// 呼び出されるThreadを指定することもできます。MainThread用には便利Methodが用意されています。
ec.registerOnMainThread(self, handler: onEvent)
}
override func viewWillDisappear(animated: Bool) {
// registerの第一引数に指定したObjectのhandler全てを解除します。
EventCenter.defaultCenter.unregister(self)
}
func updateView() {
// ...
}
func onEvent(event: MyAwesomeModel.UpdateEvent) {
self.updateView()
}
}
class MyAwesomeModel {
class UpdateEvent {}
enum StoreEvent {
case SUCCESS
case ERROR
}
func notifyUpdate() {
EventCenter.defaultCenter.post(UpdateEvent())
}
func notifyStoreResult() {
EventCenter.defaultCenter.post(StoreEvent.SUCCESS)
}
}
// If you want to see more cases, see also Tests.swift
Source
ポイント
let h = hander as? (T -> Void)
みたいな感じで、マッチする handler を簡単に選択できるということに気がついたので、
勢いで作ったという感じです。
public func post<T>(obj: T) {
for info in observers {
if let h = info.handler as? (T -> Void) {
if let queue = info.queue {
dispatch_async(queue) { h(obj) }
} else {
h(obj)
}
}
}
}
おわりに
もう少し便利メソッドなど増やしても良いかもしれません。
不具合などあれば教えていただけると幸いです。。