【问题标题】:Swift delegate protocol for generic class通用类的 Swift 委托协议
【发布时间】:2015-01-29 11:46:17
【问题描述】:

我有一个类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 == S 关联到 StateMachine 类中,但我不确定如何执行此操作,或者是否可能。我试过了:

class StateMachine<S: StateType, D: StateMachineDelegate where D.S == S> {
    ...
    weak var delegate: D?
    ...
}

但后来我被困在试图重新设计协议以正确声明StateMachine 的泛型类型。在创建StateMachine 时,必须预先声明委托的类型似乎并不正确。

【问题讨论】:

    标签: generics swift delegates protocols


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

    【讨论】:

      【解决方案2】:

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

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

      你需要在符合它的类中声明协议中定义的泛型类型的名称应该是什么。

      【讨论】:

      • 顺便说一句,鉴于状态机是解决很多问题的好设计模式,为什么要将状态限制为可散列?您失去了具有关联值的枚举的力量(或者至少您必须使它们符合可散列)。恕我直言,具有关联值的枚举非常适合状态机逻辑。
      • 谢谢,但这不起作用。我认为这个问题有点深。我的猜测是编译器不能(无论出于何种原因)保证StateMachineDelegate 的类型要求将得到满足,因为StateMachine 在这方面没有做出任何承诺。如果StateMachine 包含一个限制为StateMachineDelegate 的generic 类型参数,那么它有望在运行时被一个令人满意的类替换(但我最终得到一个奇怪的参数@987654326 @ 在委托协议中)。随着语言的发展,也许它会成为可能。
      • 关于可散列约束,我这样做是因为状态机实际上存储状态和状态转换,并检查它们的存在以确定请求的状态更改是否有效。该课程比我在问题中显示的要复杂一些,并且随着我进一步实验会变得更加复杂!如果关联的枚举很方便,那么按照您的建议将它们符合Hashable 应该不是问题。
      • 抱歉,操场崩溃如此频繁以至于它停止给我错误:-/如果委托定义关于状态机类型而不是状态类型的别名不是更正确吗? ...可悲的是,尝试这种方式对我来说是操场和编辑助理的不断崩溃:-S
      • SourceKit 对我来说也经常崩溃......显然这让编译器和我一样头疼!您将状态机定义为类型别名可能是正确的(尽管我仍然需要在委托方法中引用 State 类型,因此可以使用:typealias StateMachine: Stateful,然后是 protocol Stateful { typealias State } 和 class StateMachine&lt;S: StateType&gt;: Stateful {...}... 然后在委托中可以参考StateMachine.State)。但是委托协议仍然是通用的(具有关联的类型要求),因此不能解决问题。
      猜你喜欢
      • 2021-03-14
      • 1970-01-01
      • 2017-12-06
      • 1970-01-01
      • 2015-06-26
      • 1970-01-01
      • 1970-01-01
      • 2018-01-13
      • 1970-01-01
      相关资源
      最近更新 更多