为泛型类编写 Swift 代理协议

7
我有一个类,名为StateMachine,它是通用的,可以实现不同的状态集作为枚举。我想使用一个StateMachineDelegate协议来通知代理当状态机进入一个新状态时。 但这不能工作,因为代理协议也是带有类型要求的泛型。错误显示在声明delegate属性的位置。
protocol StateType: Hashable {}

protocol StateMachineDelegate: class {
    typealias S: StateType
    func stateMachine(stateMachine: StateMachine<S>, didEnterState newState: S)
}

class StateMachine<S: StateType> {
    typealias State = S

    weak var delegate: StateMachineDelegate?
    //~~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~
    //Protocol 'StateMachineDelegate' can only be used as a generic constraint because it has Self or associated type requirements

    var currentState: State {...}

    init(initialState: State) {...}

    func applyState(toState: State) -> Bool {
        ...
        currentState = toState
        delegate?.stateMachine(self, didEnterState: toState)
        ...
    }
}

我需要以某种方式将StateMachineDelegate.S == SStateMachine类关联起来,但我不确定如何做到这一点,或者是否可能。我尝试过:
class StateMachine<S: StateType, D: StateMachineDelegate where D.S == S> {
    ...
    weak var delegate: D?
    ...
}

但是我在重构协议以正确声明StateMachine的泛型类型时遇到了困难。在创建一个StateMachine时,事先声明委托类型似乎并不合适。

2个回答

1

看看这个解决方法是否适合你的需求,它使用@autoclosure来消除递归泛型定义的问题:

class StateMachine<S: Printable, D: StateMachineDelegate where S == D.StateType> {

    var currentState: S {
        didSet {
            // The observer
            if let delegate = self.delegate {
                delegate.stateMachine(self, didEnterState: self.currentState)
            }
        }
    }

    var delegate: D?

    init(initialState: S) {
        self.currentState = initialState
    }


}


protocol StateMachineDelegate: class {
    typealias StateType: Printable

    // Workaround with autoclosure
    func stateMachine(machine: @autoclosure() -> StateMachine<StateType, Self>, didEnterState newState: StateType)
}

final class ADelegate: StateMachineDelegate {
    typealias StateType = Int
    func stateMachine(machine: @autoclosure  () -> StateMachine<StateType, ADelegate>, didEnterState newState: StateType) {
        // Need to _unbox_ the sander from the closure
        let sender = machine()
        println(newState)
        println("State from sender: \(sender.currentState)")
    }
}

let stateMachine = StateMachine<Int, ADelegate>(initialState: 24)

stateMachine.delegate = ADelegate()
stateMachine.currentState = 50

顺便提一下,如果你购买了这个砂纸机,可能就不需要传递新状态 newState 了。我在示例中使用了可打印的 Printable 替换了 Hashable

0

我认为这只是一个名称冲突的问题... 试试这个:

protocol StateType: Hashable {}

protocol StateMachineDelegate: class {
    typealias State: StateType
    func stateMachine(stateMachine: StateMachine<State>, didEnterState newState: State)
}

class StateMachine<S: StateType> {
    typealias State = S

    weak var delegate: StateMachineDelegate?


    var currentState: State {...}

    init(initialState: State) {...}

    func applyState(toState: State) -> Bool {
        ...
            currentState = toState
        delegate?.stateMachine(self, didEnterState: toState)
        ...
    }
}

你需要声明符合协议的类中定义的泛型类型的名称。


1
顺便问一下,鉴于状态机是许多问题的良好设计模式,为什么要将状态限制为可哈希的呢?这样你就失去了具有关联值的枚举的优势(或者至少你必须使它们符合可哈希的要求)。在我看来,具有关联值的枚举对于状态机逻辑非常有用。 - Matteo Piombo
谢谢,但这不起作用。我认为问题更深层次。我的猜测是编译器不能(无论什么原因)保证StateMachineDelegate的类型要求得到满足,因为StateMachine在这方面没有做出任何承诺。如果StateMachine包括一个泛型类型参数,限制为StateMachineDelegate,那么它承诺在运行时将被替换为一个令人满意的类(但然后我会在委托协议中得到一个奇怪的StateMachine<S,D>参数)。也许随着语言的发展,这将成为可能。 - Stuart
关于可哈希的限制,我这样做是因为状态机实际上存储状态和状态转换,并检查它们的存在以确定所请求的状态更改是否有效。该类比我在问题中展示的要复杂一些,并且随着我进一步尝试,它将变得更加复杂!如果相关的枚举很方便,那么遵守您建议的Hashable也不应该成为问题。 - Stuart
抱歉,Playground 经常崩溃,以至于它停止向我提供错误信息 :-/ 如果委托人针对状态机类型而不是状态类型定义别名,这样不是更正确吗?...可惜,尝试这种方式对我来说总是导致 Playground 和编辑器助手频繁崩溃 :-S - Matteo Piombo
SourceKit 对我来说也经常崩溃...显然这让编译器和我一样头疼!你可能是对的,将状态机定义为 typealias(尽管我仍然需要在委托方法中引用“State”类型,因此可能会使用:typealias StateMachine: Stateful,然后 protocol Stateful { typealias State }class StateMachine<S: StateType>: Stateful {...}... 然后在委托中可以引用 StateMachine.State)。但是委托协议仍将是通用的(具有相关类型要求),因此无法解决问题。 - Stuart
作为一种解决方法,您可以探索将可选函数分配给状态机而不是委托的可能性 :-/ - Matteo Piombo

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接