2015-01-29 10 views
6

Ich habe eine Klasse, StateMachine, die generisch ist, um verschiedene Sätze von Zuständen wie zum Beispiel eine enum zu implementieren. Ich möchte ein StateMachineDelegate Protokoll verwenden, um einen Delegaten zu benachrichtigen, wenn der Zustandsautomat einen neuen Status eingibt.Swift Delegate-Protokoll für generische Klasse

Dies funktioniert jedoch nicht, da das Delegate-Protokoll auch generisch mit Typanforderungen ist. Der Fehler zeigt an, wo die delegate-Eigenschaft deklariert ist.

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



Ich brauche irgendwie, dass StateMachineDelegate.S == S in der StateMachine Klasse assoziieren, aber ich bin nicht sicher, wie dies zu tun, oder wenn es möglich ist. Ich habe versucht:

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

aber dann stecken ich versucht, das Protokoll zu überarbeiten, um richtig die allgemeine Art der StateMachine zu erklären. Und es scheint nicht richtig zu sein, den Typ des Delegierten bei der Erstellung eines StateMachine im Voraus zu deklarieren.

Antwort

0

Ich denke, es ist nur ein Name Kollisionsproblem ... try this:

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

Sie müssen die Namen der gattungsgemäßen Art zu erklären, was im Protokoll definiert in der Klasse es konform sein sollten.

+1

Übrigens, angesichts dieser State Machine ist ein gutes Design-Muster für eine Menge Probleme, warum den Zustand zu beschränken, um hashable? Du verlierst die Macht der Enums mit den zugehörigen Werten (oder zumindest musst du sie mit "hashable" anpassen). IMHO-Enums mit zugehörigen Werten eignen sich hervorragend für State-Machine-Logiken. –

+0

Danke, aber das funktioniert nicht. Ich denke, das Problem läuft ein bisschen tiefer. Meine Vermutung ist, dass der Compiler (aus welchen Gründen auch immer) nicht garantieren kann, dass die Anforderungen des 'StateMachineDelegate'-Typs erfüllt werden, da' StateMachine' in dieser Hinsicht keine Versprechungen macht. Wenn 'StateMachine' einen _generic_ type-Parameter enthält, der auf' StateMachineDelegate' beschränkt ist, dann ist es vielversprechend, dass er zur Laufzeit durch eine befriedigende Klasse ersetzt wird (aber dann lande ich im Delegate-Protokoll mit einem ungeraden Parameter von 'StateMachine '). . Vielleicht wird es möglich werden, wenn sich die Sprache entwickelt. – Stuart

+0

In Bezug auf die hashbare Einschränkung habe ich dies getan, weil die Zustandsmaschine tatsächlich Zustände und Zustandsübergänge speichert und auf ihre Existenz prüft, um festzustellen, ob eine angeforderte Zustandsänderung gültig ist. Die Klasse ist ein bisschen komplexer als ich in der Frage gezeigt habe, und wird komplexer, während ich weiter experimentiere! Wenn verknüpfte Aufzählungen nützlich sind, sollte es kein Problem sein, sie an "Hashable" anzupassen, wie Sie es vorschlagen. – Stuart

1

Sehen Sie, wenn diese Abhilfe für Ihre Bedürfnisse in Ordnung ist, es @autoclosure verwendet loswerden ein Problem mit rekursive allgemeinen Definitionen zu erhalten:

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 

By the way, die Ansicht, dass, wenn Sie den Schleifer zu bekommen, haben Sie wahrscheinlich don muss nicht newState übergeben werden. Ich habe Printable anstelle von Hashable für das Beispiel verwendet.