2016-03-26 16 views
1

これは私が欲しいものです。私は非常に簡単なevent dispatcherを書いています(私のコードを見るにはそのリンクをクリックしてください)。私がlisten()fire()の方法しか持っていなかったときにうまくいきました。これは、あなたがそれを使用することができる方法である:クラスプロパティ配列をジェネリック型の異種配列にするにはどうすればよいですか?

struct UserHasBirthday: Event { 
    let name: String 

    init(name: String) { 
     self.name = name 
    } 
} 

let events = TestDispatcher() 
events.listen { 
    (event: UserHasBirthday) in 
    print("Happy birthday \(event.name)!") 
} 
events.fire(UserHasBirthday(name: "John Doe")) 

すべてが順調と良いのですが、今私はあなたがキューにイベントをプッシュし、後で一度にすべてを発射できた機能を追加したいと。そのため、私はpushメソッドとflushメソッドを追加しました。

ここで問題は、flush()メソッドでは、指定された特定のイベントタイプに汎用のEventタイプをダウンキャストできなければならないということです。そうでないと、fire()メソッドは機能しません。

私はおそらく、イベント情報と同じ配列に型情報を保存できると考えました。ご覧のとおり、私はタプルでそれをしようとしました。残念ながら、それはそのように動作しません。

私は、変数pushedEventsのような汎用型を受け入れる方法を見つけることができたと思う:var pushedEvents = Array<E: Event>()それは動作する可能性があります。しかし、私が知る唯一の方法は、汎用クラスをクラス全体に割り当てることです:class TestDispatcher<E: Event> { }ですが、そのクラスのすべてのインスタンスは1つの特定のタイプのイベントにしか使用できません。

誰かがこの仕事をするために何らかの方法を知っていますか?

答えて

1

This guy on redditは、私がそのパターンについては知りませんでした(いわゆるタイプの消去パターンを使用して私に解決策を与えました)。

私はもっと自分のニーズを満たすために自分のコードを編集し、これは私が今持っているものです。

public protocol Event {} 

public protocol ErasedListener { 
    func matches(eventType: Event.Type) -> Bool 
    func dispatchIfMatches(event: Event) 
} 

public struct Listener<T: Event>: ErasedListener { 
    let dispatch: T -> Void 

    public func matches(eventType: Event.Type) -> Bool { 
     return matches(String(eventType)) 
    } 

    func matches(eventType: String) -> Bool { 
     return eventType == String(T.self) 
    } 

    public func dispatchIfMatches(event: Event) { 
     if matches(String(event.dynamicType)) { 
      dispatch(event as! T) 
     } 
    } 
} 

public protocol Dispatcher { 
    func listen<E: Event>(listener: E -> Void) 
    func fire(event: Event) 
    func queue<E: Event>(event: E) 
    func flushQueueOf<E: Event>(eventType: E.Type) 
    func flushQueue() 
    func forgetListenersFor<E: Event>(event: E.Type) 
    func emptyQueueOf<E: Event>(eventType: E.Type) 
    func emptyQueue() 
} 

public class MyDispatcher: Dispatcher { 
    var listeners = [ErasedListener]() 
    var queuedEvents = [Event]() 

    public init() {} 

    public func listen<E: Event>(listener: E -> Void) { 
     let concreteListener = Listener(dispatch: listener) 

     listeners.append(concreteListener as ErasedListener) 
    } 

    public func fire(event: Event) { 
     for listener in listeners { 
      listener.dispatchIfMatches(event) 
     } 
    } 

    public func queue<E: Event>(event: E) { 
     queuedEvents.append(event) 
    } 

    public func flushQueue() { 
     for event in queuedEvents { 
      fire(event) 
     } 
     emptyQueue() 
    } 

    public func emptyQueue() { 
     queuedEvents = [] 
    } 

    public func flushQueueOf<E: Event>(eventType: E.Type) { 
     for event in queuedEvents where String(event.dynamicType) == String(eventType) { 
      fire(event) 
     } 
     emptyQueueOf(eventType) 
    } 

    public func forgetListenersFor<E: Event>(eventType: E.Type) { 
     listeners = listeners.filter { !$0.matches(eventType) } 
    } 

    public func emptyQueueOf<E: Event>(eventType: E.Type) { 
     queuedEvents = queuedEvents.filter { String($0.dynamicType) != String(eventType) } 
    } 
} 

使用例をご返信用

struct UserDied: Event { 
    var name: String 
} 

class UserWasBorn: Event { 
    let year: Int 

    init(year: Int) { 
     self.year = year 
    } 
} 

// you can use both classes and structs as events as you can see 

let daveDied = UserDied(name: "Dave") 
let bartWasBorn = UserWasBorn(year: 2000) 

var events = MyDispatcher() 

events.listen { 
    (event: UserDied) in 

    print(event.name) 
} 

events.listen { 
    (event: UserWasBorn) in 

    print(event.year) 
} 

events.queue(daveDied) 
events.queue(UserWasBorn(year: 1990)) 
events.queue(UserWasBorn(year: 2013)) 
events.queue(UserDied(name: "Evert")) 

// nothing is fired yet, do whatever you need to do first 

events.flushQueue() 
/* 
    This prints: 
    Dave 
    1990 
    2013 
    Evert 
*/ 

// You could also have flushed just one type of event, like so: 
events.flushQueueOf(UserDied) 
// This would've printed Dave and Evert, 
// but not the year-numbers of the other events 
1

問題は、Swiftはメタタイプへの型変換を許可しないということです。

回避策の一つは、あなたのTestDispatcherクラスのflush()機能でスイッチケースには(少なくともそれらがあなたのDispatcherに使用します)Eventに準拠するすべてのタイプを含めることです。それは私があなたが探していると信じている機能と同様に万能ではなく、as you've shown with your own answer、タイプの消去はここへの道です。しかし、メタタイプにキャストしようとする独自のアプローチがうまくいかなかった理由を説明しているので、元の回答はそのまま残しておきます。

public protocol Event {} 

public enum Listener<E: Event> { 
    public typealias T = E ->() 
} 

public protocol Dispatcher { 
    func listen<E: Event>(listener: Listener<E>.T) 
    func fire<E: Event>(event: E) 
    func push<E: Event>(event: E) 
    func flush() 
} 

//

public class TestDispatcher: Dispatcher { 
    var listeners = [String:[Any]]() 
    var pushedEvents = [Event]() 

    public init() {} 

    public func listen<E: Event>(listener: Listener<E>.T) { 
     var listeners = self.listeners[String(E.self)] ?? [] 
     listeners += [listener] as [Any] 
     self.listeners[String(E.self)] = listeners 
    } 

    public func fire<E: Event>(event: E) { 
     listeners[String(E.self)]?.forEach { 
      let f = $0 as! Listener<E>.T 
      f(event) 
     } 
    } 

    public func push<E: Event>(event: E) { 
     pushedEvents = pushedEvents + [event] 
    } 

    /* Include a switch case over all types conforming to Event ... */ 
    public func flush() { 
     for event in pushedEvents { 
      switch event { 
      case let ev as UserHasBirthday: fire(ev) 
      case let ev as UserWonTheLottery: fire(ev) 
      case _: print("Unknown event type.") 
      } 
     } 
    } 
} 

使用例:

struct UserHasBirthday: Event { 
    let name: String 

    init(name: String) { 
     self.name = name 
    } 
} 

struct UserWonTheLottery: Event { 
    let name: String 
    let amount: Int 

    init(name: String, amount: Int) { 
     self.name = name 
     self.amount = amount 
    } 
} 

let events = TestDispatcher() 
events.listen { 
    (event: UserHasBirthday) in 
    print("Happy birthday \(event.name)!") 
} 
events.listen { 
    (event: UserWonTheLottery) in 
    print("Congratulations \(event.name) for winning \(event.amount)!") 
} 

events.push(UserHasBirthday(name: "John Doe")) 
events.push(UserHasBirthday(name: "Jane Doe")) 
events.push(UserWonTheLottery(name: "Jane Doe", amount: 42000)) 

events.flush() 
/* Happy birthday John Doe! 
    Happy birthday Jane Doe! 
    Congratulations Jane Doe for winning 42000! */ 
+0

感謝を。それはイベントのディスパッチャーがほんの少しのイベントに限定されていれば、私にとっては役に立たないものですから、それは私のためにカットするつもりはありません。その場合は、プッシュ/フラッシュ機能を持っていないと思います。 私はあなたにそれを周回させました。なぜなら、おそらくそれのまわりに他の方法はないからです。しかし、私はあなたの答えをまだ受け入れられていないとマークしていません。なぜなら、これはいくつかのハッキーなやり方でやり遂げることができるという希望に執着しているからです。私は一般的なタイプアリアを作成するためにenumを使用したのと同じです。 – Evert

+0

@Evert私はそれを理解することができます。また、この質問を1日程度開いたままにしておくことをお勧めします。誰かがより価値のあるハッキー入力を与えることができるかもしれません(私は「_possibly_これは、上記のように実際に私が考えなかった構造ラッパーの回避策があるかもしれません。 – dfri

+0

あなたが興味を持っている場合に備えて、私は実際の回答[ここ](http://stackoverflow.com/a/36242803/1498652)を投稿しました。 – Evert