如何使类属性数组成为泛型类型的异构数组?

时间:2016-03-26 22:38:35

标签: arrays swift generics

这就是我想要的。我写的是一个非常简单的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") )

这一切都很好,但现在我想添加一个功能,你可以将事件推送到队列,然后立即将它们全部解雇。这就是我添加推送和刷新方法的原因。

现在的问题是,在flush()方法中,我需要能够将通用Event类型向下转换为给定的特定事件类型。否则fire()方法无效。

所以我想,也许我可以将类型信息保存在与事件本身相同的数组中。正如你所看到的,我试图用元组做到这一点。不幸的是,它不能那样工作。

我认为,如果我能找到一种方法让变量pushedEvents接受这样的通用类型:var pushedEvents = Array<E: Event>()那么它可以工作。但我知道这样做的唯一方法是将这个泛型分配给整个类,如:class TestDispatcher<E: Event> { },但那个类的每个实例只能用于一个特定类型的事件,我绝对不会想要那个。

有人知道某种方法可以使这项工作吗?

2 个答案:

答案 0 :(得分:1)

问题是Swift不允许将类型转换为元类型。

一种解决方法是在{{1}的Event函数的开关案例中包含符合Dispatcher的所有类型(至少是您在flush()中使用的那些类型) } .class。它不像我认为你正在寻找的功能那样通用,而as you've shown with your own answer,类型擦除就是通往这里的方式。但是,我将保留原始答案,因为它解释了为什么您尝试转换为元类型的原始方法不起作用。

TestDispatcher

//

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.")
            } 
        }
    }
}

答案 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