Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Swift delegate protocol for generic class

I have a class, StateMachine, that is generic to allow for different sets of states to be implemented as, for example, an enum. I want to use a StateMachineDelegate protocol to notify a delegate when the state machine enters a new state.

But this doesn't work since the delegate protocol is also generic with type requirements. The error shows where the delegate property is declared.

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



I need to somehow associate that StateMachineDelegate.S == S in the StateMachine class, but I'm not sure how to do this, or if it's possible. I tried:

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

but then I get stuck trying to rework the protocol to properly declare the generic type of StateMachine. And it doesn't seem right to have to declare the type of the delegate up front when creating a StateMachine.

like image 535
Stuart Avatar asked Jan 29 '15 11:01

Stuart


1 Answers

See if this workaround is ok for your needs, it uses @autoclosure to get rid of a problem with recursive generic definitions:

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, consider that if you get the sander, probably you don't need to get the newState passed. I used Printable in place of Hashable for the example.

like image 120
Matteo Piombo Avatar answered Oct 16 '22 04:10

Matteo Piombo