イベントリスナー/Swift勉強

Swiftのお勉強ということでイベントリスナー的なクラスを書いてみました。
- generic
- protocol
- closure
- operator overloading
あたりを試した感じです。

### リスナークラス

まずイベント通知を受け取るリスナークラス

```swift
// イベントの通知を受け取る
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
    }
}
```

### イベント管理
次にリスナーの登録先、イベントの発行のためのクラス

```swift
// 登録されたリスナーにイベントを通知する
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のお試しがてら同期のための仕組みも書いてみる。
同期の必要がなければ空定義のクラスを使えばよいといったもの

```swift
protocol LockProtocol
{
    init()
    func lock()
    func unlock()
}
// なにもしない(同期の必要がない場合に利用する)
class DummyLock : LockProtocol
{
    required init(){}
    func lock(){}
    func unlock(){}
}
```

### 使い方

```swift
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は任意
```

```shell-session
hi hoge
bonjour hoge
```

###演算子オーバーロード

```swift
myEventStr += strListener2
```
みたいに書きたい場合は **globalスコープ**で定義。クラス内で書くとエラーになってかなり悩まされました。generic引数を書き直さないといけない（？）感じでうーん

```swift
// 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 が呼ばれるタイミングがコントロールできなくて、この方法使えないんですね…。

[Swiftのautoreleasepool等で注意する点](http://qiita.com/takabosoft/items/02628c1e188b0d6c5775) 

で紹介されていたclosureを使う方法とかになるようで。

----

C#の

```csharp
timer.Tick += new EventHandler(timer_tick);
timer.Tick -= new EventHandler(timer_tick);
```
みたいに情報を保持することをしなくてもリスナーの add / remove ができると便利なんですが…さて

＃Objective-C未経験でいきなりSwiftから始めるとAPI探しが大変ですね
＃playground様々だけどXCode6正式版でもよく落ちる…