iOSアプリの開発で、あるオブジェクトから他のオブジェクトへイベントを送ったりそれを受け取ったりしたい場合、通常NSNotificationCenterを使うと思います。
しかし使い勝手がよいとはあまり言えないのではないでしょうか。
理由としては...
- タイプセーフじゃない
- 送る側の
userInfo:
のデータの種類を変えても受け取る側がコンパイルエラーにならない
- 送る側の
- 引数が多い
-
object:
って何? 送るオブジェクトを指定するんじゃないの?
-
- 利用が終わったら
removeObserver:
しないといけない- 忘れがち
なので上記を解決する EventHub というライブラリを作ってみました。
ライブラリ作成にあたってこの記事を大いに参考にさせてもらいました。
EventCenter: AndroidのEventBusのSwift版っぽいのを作りました - Qiita
また、 SwiftEventBus やAndroidの EventBus からも影響を受けています。
リポジトリ
https://github.com/mishimay/EventHub
実体: https://github.com/mishimay/EventHub/blob/master/Pod/Classes/EventHub.swift
テスト: https://github.com/mishimay/EventHub/blob/master/Example/Tests/Tests.swift
インストール
CocoaPodsに登録されています。
use_frameworks!
pod "EventHub"
環境
Swift 2.1
EventHubの特徴
- タイプセーフ
-
EventType
というプロトコルを準拠させたclass
,struct
,enum
を送受信する - それらオブジェクトにデータを持たせればデータの送受信をタイプセーフに行うことが可能
-
- 管理が楽
- observerであるオブジェクトが破棄されたらEventHubからも自動的に削除される
- よって
removeObserver:
などのメソッドを呼ぶ必要がない
- 軽量
- 実体はSwiftファイル1つだけ
簡単な例
struct MessageEvent: EventType {
let message: String
}
EventHub.addObserver(self) { (event: MessageEvent) in
print(event.message) // -> 😜
}
EventHub.post(MessageEvent(message: "😜"))
使い方
-
イベントを定義
EventType
プロトコルに準拠させてください。
class
でもstruct
でもenum
でも構いません。
ここで定義したイベントのオブジェクトをpostしたり受け取ったりします。enum LoginEvent: EventType { case Success(id: String) case Failure(error: ErrorType) }
-
observerを追加
addObserver(observer:thread:block:)
メソッドを呼びます。
-
observer
: イベントを受け取るするオブジェクト。もしこのオブジェクトが破棄されたらEventHubからも自動的に削除されてblock:
で渡す処理も実行されなくなります。 -
thread
: 指定は任意です (デフォルト値はnil)。どのスレッドで処理を実行するか指定します。もしnilだった場合、処理はイベントがpostされたスレッドと同じスレッドで同期的に実行されます。 -
block
: イベントを受け取ったときに実行されるクロージャです。postされたイベントオブジェクトを受け取ります。
```swift
EventHub.addObserver(self, thread: .Main) { (event: LoginEvent) in
switch event {
case .Success(let id):
print(id)
case .Failure(let error):
print(error)
}
}
```
-
イベントを送信
EventHub.post(LoginEvent.Success(id: id))
コードの説明
内部ではNSNotificationCenterを利用せず、自前で送受信の仕組みを書いています。
// EventTypeプロトコルの定義です。
// 制約はありません。
// EventTypeプロトコルを用意した理由としては、もしどんなオブジェクトでpostできるようにすると
// 「渡すオブジェクトの型が同じだけどイベントの種類が違う」ものがある場合にそれらのイベントが
// 区別しにくくなると考えたからです。
public protocol EventType {}
// どのスレッドでobserveするかを指定するためのenumです。
// .Backgroundを指定する場合、associated value にqueueを指定できます。
// e.g. .Background(queue: dispatch_queue_create("com.example", nil))
public enum Thread {
case Main
case Background(queue: dispatch_queue_t?)
private var queue: dispatch_queue_t {
switch self {
case .Main:
return dispatch_get_main_queue()
case .Background(let queue):
return queue ?? dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)
}
}
}
public struct EventHub {
// 登録されたobserver情報を管理するためのstructです。
private struct Observation {
weak var observer: AnyObject?
let thread: Thread?
let block: Any
}
// 登録されたobserver情報を配列で持っておきます。
private static var observations = [Observation]()
// observerの登録
public static func addObserver<T: EventType>(observer: AnyObject, thread: Thread? = nil, block: T -> ()) {
observations.append(Observation(observer: observer, thread: thread, block: block))
}
// observerの解除
// 任意のタイミングでobserverを解除したい場合に使用します。
public static func removeObserver(observer: AnyObject) {
observations = observations.filter { $0.observer! !== observer }
}
// イベントの送信
public static func post<T: EventType>(event: T) {
// まずobserverがnilになっている (=破棄されている) ものを取り除きます。
observations = observations.filter { $0.observer != nil }
// イベントの型が一致するblockに対してイベントを送信します。
observations.forEach {
if let block = $0.block as? T -> () {
// スレッド指定があればそのスレッドで非同期的に、
// スレッド指定がなければ同じスレッドで同期的にblockを実行します。
if let queue = $0.thread?.queue {
dispatch_async(queue) {
block(event)
}
} else {
block(event)
}
}
}
}
}
おわりに
結構便利なものができたんじゃないかと思います。
最初NSNotificationCenterを使って作ろうと思いましたがむしろ使わないほうが簡単に書けました。
これからどんどん使っていきたいです。
※ もともとEventKitという名前でしたが、同名のiOSのフレームワークがあったのでEventHubに変えました。詳しくはコメント欄を参照してください。