2015-01-29 14 views
6

Ho una classe, StateMachine, che è generica per consentire l'implementazione di diversi set di stati come, ad esempio, un enum. Voglio utilizzare un protocollo StateMachineDelegate per informare un delegato quando la macchina di stato entra in un nuovo stato.Protocollo Swift delegato per classe generica

Ma questo non funziona poiché il protocollo delegato è anche generico con i requisiti del tipo. L'errore mostra dove viene dichiarata la proprietà 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) 
     ... 
    } 
} 



ho bisogno di associare in qualche modo che StateMachineDelegate.S == S nella classe StateMachine, ma non sono sicuro come fare questo, o se è possibile. Ho provato:

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

ma poi mi si blocca cercando di rielaborare il protocollo di dichiarare correttamente il tipo generico di StateMachine. E non sembra giusto dover dichiarare in anticipo il tipo di delegato durante la creazione di uno StateMachine.

risposta

0

penso che il suo solo un problema di collisione nome ... provate questo:

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

è necessario dichiarare qual è il nome del tipo generico definito nel protocollo dovrebbe essere nella classe conforme ad esso.

+1

A proposito, dato che la macchina a stati è un buon modello di progettazione per molti problemi, perché limitare lo stato ad essere lavabile? Perdi il potere dell'enumerazione con valori associati (o almeno dovrai conformarli a hashable). Le enumerazioni IMHO con i valori associati sono ottime per le logiche delle macchine a stati. –

+0

Grazie, ma questo non funziona. Penso che il problema sia un po 'più profondo. La mia ipotesi è che il compilatore non possa (per qualsiasi motivo) garantire che i requisiti di tipo 'StateMachineDelegate' saranno soddisfatti, dato che' StateMachine' non promette nulla al riguardo. Se 'StateMachine' includeva un parametro di tipo _generico_ vincolato a' StateMachineDelegate', allora è promettente che verrà sostituito con una classe soddisfacente in fase di esecuzione (ma poi finisco con un parametro dispari di 'StateMachine ' nel protocollo delegato) . Forse diventerà possibile man mano che la lingua si sviluppa. – Stuart

+0

Per quanto riguarda il vincolo hashable, l'ho fatto perché la macchina di stato memorizza effettivamente stati e transizioni di stato e controlla la loro esistenza per determinare se una modifica dello stato richiesta è valida. La lezione è un po 'più complessa di quella che ho mostrato nella domanda, e diventerà più complessa man mano che sperimenterò ulteriormente! Se le enumerazioni associate sono a portata di mano, non dovrebbe essere un problema conformarle a 'Hashable', come suggerisci tu. – Stuart

1

Vedere se questa soluzione è ok per le vostre esigenze, utilizza @autoclosure per sbarazzarsi di un problema con le definizioni ricorsive generiche:

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 

A proposito, considerare che se si ottiene la levigatrice, probabilmente don Non è necessario far passare il newState. Per l'esempio ho utilizzato Printable al posto di Hashable.