こんにちは、きんくまです。
イベント管理のライブラリを探していたら、こんなページが見つかりました。
>> Swift Toolbox
その中にEmitterKitというライブラリがありまして、見てみたら使いやすそうでした。
>> aleclarson/emitter-kit
試してみたので、メモです。
インストール方法はgithubのページを参照してください。
簡単な使い方 Signal
イベントには2種類あり、SignalとEventです。
Signalはイベント発火時に引数なしで呼ばれるもの
Eventは引数ありで呼ばれるものです。
Signalを使ったサンプル
import Foundation import EmitterKit class Car{ let runSignal = Signal() func run(){ runSignal.emit() } } class Driver{ var car:Car! var runCount:Int = 0 let carListener:Listener! init(){ car = Car() //once sample --- car.runSignal.once({ _ in self.runCount += 1 println("car runs in once closure") }) car.run() println("1st count: \(self.runCount)") car.run() println("2nd count: \(self.runCount)") println("-----------") //on sample --- self.runCount = 0 //you must retain Listener returned from on self.carListener = car.runSignal.on({ _ in self.runCount += 1 println("car runs in on closure") }) car.run() println("1st count: \(self.runCount)") car.run() println("2nd count: \(self.runCount)") //After listener is set to nil, events are not emitterred self.carListener = nil car.run() println("3st count: \(self.runCount)") car.run() println("4nd count: \(self.runCount)") } }
サンプル実行
let driver = Driver()
出力
car runs in once closure 1st count: 1 2nd count: 1 ----------- car runs in on closure 1st count: 1 car runs in on closure 2nd count: 2 3st count: 2 4nd count: 2
イベント発行時に実行するクロージャへはonとonceの2種類の登録方法があります。
onは何回も呼ばれる
onceは1度呼ばれたらおしまい
このとき、注意するのはonを登録したときに返ってくるListener型の変数を、受けとり側がプロパティとして持つ必要があるということです。
onceはその必要はないです。
上のサンプルでも、受け取り側のDriverインスタンスのcarListenerをnilにすると、それ以降はonであってもクロージャーは呼ばれません。
引数つけてよぶ Event
サンプル
import Foundation import EmitterKit class Calculator{ let addEvent = Event<Int>() var result:Int = 0 func add(num:Int){ result += num addEvent.emit(self.result) } } class CalcUser{ let calc = Calculator() let calcListener:Listener! init(){ self.calcListener = self.calc.addEvent.on{ (result:Int) in println("calc is added \(result)") } self.calc.add(10) self.calc.add(5) } }
サンプル実行
let user = CalcUser()
出力
calc is added 10 calc is added 15
Eventの引数はGenericsなので、どんな型でもOKです。(文法用語が正しいかわかんないけど)
この場合はInt型を引数にとるようにしました。
また、emitter, on, onceは引数を2つとることも可能です。
その場合は、第一引数に対象オブジェクト(AnyObject)、第二引数にemitter, on/once間でやりとりする変数を設定できます。
複雑な例
今度は複数のオブジェクトが1つのオブジェクトの複数のプロパティを監視する例です。
emit, on に2つの引数をとって、プロパティ名による振り分けを行いました。
で、ここでちょっと詰まったのですが、第一引数がオブジェクトの場合はそれほど問題ないのですが、文字列にしたいとき、クラスプロパティだとだめで、さらにletなインスタンスプロパティだけでもだめで、それをさらにStringでキャストしないと駄目でした。これは何でなのかはわかりません、、。ソースコードを読んでみたらHelpers.swiftの中にgetHashというメソッドがあり、これがString(“foo”) と “foo” を区別しているっぽいのですが、どうなんでしょう。
このあたり、AnyObjectとAnyという違いがあるのかもしれない、、。うーん。
ともかくサンプルです。
import Foundation import EmitterKit class Person{ let PROP_AGE = String("age") let PROP_TITLE = String("title") let propEvent = Event<(target:AnyObject, value:Any)>() init(title:String, age:Int){ self.title = title self.age = age } var title:String{ didSet{ propEvent.emit(self.PROP_TITLE, (self, self.title)) } } var age:Int{ didSet{ propEvent.emit(self.PROP_AGE, (self, self.age)) } } } class Client{ var listeners:[String:Listener!] = [:] var name:String var targetPerson:Person! init(name:String, targetPerson:Person){ self.name = name self.targetPerson = targetPerson self.registerEvents() } func registerEvents(){ let propTitle = self.targetPerson.PROP_TITLE self.listeners[propTitle] = self.targetPerson.propEvent.on(propTitle, { (target:AnyObject, value:Any) in println("person's title changed:\n\tclient: \(self.name), target: \(target), value: \(value)") }) let propAge = self.targetPerson.PROP_AGE self.listeners[propAge] = self.targetPerson.propEvent.on(propAge, { (target:AnyObject, value:Any) in println("person's age changed:\n\tclient: \(self.name), target: \(target), value: \(value)") }) } } class ComplexSampleMain{ init(){ let person = Person(title: "CEO", age: 61) let client1 = Client(name: "Taro", targetPerson:person) let client2 = Client(name: "Hanako", targetPerson:person) person.title = "COO" person.age = 48 } }
サンプル実行
let m = ComplexSampleMain()
出力
person's title changed: client: Taro, target: EmitterKitSample.Person, value: COO person's title changed: client: Hanako, target: EmitterKitSample.Person, value: COO person's age changed: client: Taro, target: EmitterKitSample.Person, value: 48 person's age changed: client: Hanako, target: EmitterKitSample.Person, value: 48
EmitterKitの悩ましいところは、onでイベント監視するときに必ず監視側がListenerオブジェクトを保持する必要があるということです。(繰り返しになるのですが、onceは必要ないです。)なので、監視するプロパティが多いとその分Listenerオブジェクトも増えていきます。そこで、Dictionaryを作ってそいつで複数登録・管理をするようにしてみました。
で、もし監視の必要がなくなればそのDictinaryの該当部分を削除すれば大丈夫です。
またemitterと on/once間でやりとりするのは、classやTupleなどもやりとりできます。なので、この例ではTupleを使ってやっていますが、それらプロパティをまとめてやりとりするクラスを作ってみるのもいいと思います。
そのほかの機能
この他に、NSObjectのサブクラスであれば、今回のようにwillSet/didSetなどをつかわなくてもプロパティ監視ができるみたいです。
あとNSNotificationCenterを便利に使えるみたいです。
■ 自作iPhoneアプリ 好評発売中!
・フォルメモ - シンプルなフォルダつきメモ帳
・ジッピー電卓 - 消費税や割引もサクサク計算!
■ LINEスタンプ作りました!
毎日使える。とぼけたウサギ