【问题标题】:How can I make a class property array be a heterogeneous array of a generic type?如何使类属性数组成为泛型类型的异构数组?
【发布时间】:2016-03-26 22:38:35
【问题描述】:

这就是我想要的。我正在写一个非常简单的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> { },但是该类的每个实例只能用于一种特定类型的事件,我绝对不希望那样.

有没有人知道某种方法来完成这项工作?

【问题讨论】:

    标签: arrays swift generics


    【解决方案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
    

    【讨论】:

      【解决方案2】:

      问题在于 Swift 不允许类型转换为元类型。

      一种解决方法是在TestDispatcher 类的flush() 函数的switch case 中包含符合Event 的所有类型(至少是您将在Dispatcher 中使用的类型)。它不像我相信您正在寻找的功能那样通用,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! */
      

      【讨论】:

      • 感谢您的回复。这对我来说不会削减它,因为如果事件调度器仅限于几个事件,它对我来说有点没用。在那种情况下,我宁愿没有推送/刷新功能。我赞成你,因为也许真的没有其他办法。但是我还没有将您的答案标记为已接受,因为我仍然抱有一些希望,希望这可以以某种骇人听闻的方式完成。就像我使用枚举来创建通用类型别名一样。
      • @Evert 我可以理解,我也认为你应该让这个问题保持开放一天左右,也许其他人可以提供更有价值的 hacky-input(我使用了“”这个词可能它没有比上面更好的了“因为实际上可能有一些我没有考虑过的结构包装解决方法:)。
      • 如果您有兴趣,我已经发布了一个有效的答案here
      • @Evert:很好,昨天我确实在脑海中短暂地浮现了使用类型擦除,但我不太熟悉这个概念,所以我放弃了。此外,擦除类型使用的每个附加方法往往会产生一些样板预兆,但在这种情况下,我想说这可能是最好的解决方案,干得好(对你和 reddit 的家伙 :)。如果可能,请添加一个使用示例(原样:仅侦听器框架)来回答您的问题,然后我相信您应该接受它作为回答您的问题。
      • 它说“你明天可以接受你的答案”。所以如果我记得,我会这样做。
      猜你喜欢
      • 2021-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-05-25
      • 1970-01-01
      • 2019-12-12
      • 1970-01-01
      • 2019-03-31
      相关资源
      最近更新 更多