Swiftのお勉強ということでイベントリスナー的なクラスを書いてみました。
- generic
- protocol
- closure
- operator overloading
あたりを試した感じです。
リスナークラス
まずイベント通知を受け取るリスナークラス
// イベントの通知を受け取る
class EventListener <EventArgType>{
typealias EventMethod = (sender: AnyObject!, args: EventArgType!) -> ()
typealias IdType = UInt64
let invoke: EventMethod!
internal var id: IdType? // 登録状況やリスナーを識別するための情報 nilなら未登録
init( callback: EventMethod! ){
self.invoke = callback
self.id = nil
}
}
イベント管理
次にリスナーの登録先、イベントの発行のためのクラス
// 登録されたリスナーにイベントを通知する
class Event <EventArgType, LockType where LockType : LockProtocol>{
typealias SelfType = Event<EventArgType, LockType>
typealias ListenerType = EventListener<EventArgType>
typealias IdType = EventListener<EventArgType>.IdType
private var listeners = Dictionary<IdType, EventListener<EventArgType>>()
private var uniqueId : IdType = 0 // id発行に使う
private var locker = LockType()
init(){
}
// リスナー登録
func add(listener : ListenerType) -> Bool{
var result = false;
locker.lock()
result = addImpl(listener)
locker.unlock()
return result
}
// リスナー削除
// ToDo: removeの為にlistener覚えておくのはもったいないのでIdにしたほうがいいかも
func remove(listener : ListenerType) -> Bool{
var result = false;
locker.lock()
result = removeImpl(listener)
locker.unlock()
return result
}
// イベント発行
func trigger(sender: AnyObject!, args: EventArgType!){
locker.lock()
triggerImpl(sender, args : args )
locker.unlock()
}
private func addImpl(listener : ListenerType) -> Bool{
if listener.id != nil { return false }
let id = issueId()
listeners[ id ] = listener
listener.id = id
return true;
}
private func removeImpl(listener : ListenerType) -> Bool{
if listener.id == nil { return false }
listeners.removeValueForKey(listener.id!)
listener.id = nil
return true;
}
private func triggerImpl(sender: AnyObject!, args: EventArgType!){
for listener in listeners.values{
listener.invoke( sender: sender, args: args )
}
}
// リスナーを識別するためのID発行
private func issueId() -> IdType{
do {
uniqueId++
if listeners[uniqueId] == nil { return uniqueId }
}while(true) // ToDo
}
}
単純に add(), remove() でリスナーの付け外しをして
trigger()でイベントを通知するといったもの
protocolの検証として同期機構
protocolのお試しがてら同期のための仕組みも書いてみる。
同期の必要がなければ空定義のクラスを使えばよいといったもの
protocol LockProtocol
{
init()
func lock()
func unlock()
}
// なにもしない(同期の必要がない場合に利用する)
class DummyLock : LockProtocol
{
required init(){}
func lock(){}
func unlock(){}
}
使い方
var myEventStr = Event<String, DummyLock>() // 同期なし、文字列通知を受け取るイベント
var strListener1 = EventListener<String>( { sender, args in println("hi \(args)") })
var strListener2 = EventListener<String>( { sender, args in println("hello \(args)") })
var strListener3 = EventListener<String>( { sender, args in println("bonjour \(args)") })
myEventStr.add( strListener1 )
myEventStr.add( strListener2 )
myEventStr.add( strListener3 )
myEventStr.remove( strListener2 )
myEventStr.trigger(sender, args: "hoge") // senderは任意
hi hoge
bonjour hoge
###演算子オーバーロード
myEventStr += strListener2
みたいに書きたい場合は globalスコープで定義。クラス内で書くとエラーになってかなり悩まされました。generic引数を書き直さないといけない(?)感じでうーん
// add event(operator)
func +=<EventArgType, LockType where LockType : LockProtocol> (
inout handler : Event<EventArgType, LockType>,
event : EventListener<EventArgType>)
{
handler.add( event )
}
// remove event(operator)
func -=<EventArgType, LockType where LockType : LockProtocol> (
inout handler : Event<EventArgType, LockType>,
event : EventListener<EventArgType>)
{
handler.remove( event )
}
###演算子オーバーロード時のエラー対処は…?
上記例ではadd()/remove()では戻り値で成功・失敗を知ることができますが、+=や-=は結果を放置しています。
C#は例外で通知する作りですが、Swiftの場合は演算子オーバーロード関数内でエラーになったらどうするのがいいんでしょうね…
###おまけ:AutoLockみたいなもの
C++だとコンストラクタとデストラクタで lock(), unlock() を呼ぶ AutoLockクラスみたいなのがあって、一行でスコープ範囲の保護をするなんてのがありますけど、SwiftはARCだから deinit が呼ばれるタイミングがコントロールできなくて、この方法使えないんですね…。
で紹介されていたclosureを使う方法とかになるようで。
C#の
timer.Tick += new EventHandler(timer_tick);
timer.Tick -= new EventHandler(timer_tick);
みたいに情報を保持することをしなくてもリスナーの add / remove ができると便利なんですが…さて
#Objective-C未経験でいきなりSwiftから始めるとAPI探しが大変ですね
#playground様々だけどXCode6正式版でもよく落ちる…