user626776
user626776

Reputation:

Swift how to use generic protocol in generic class

I am writing a library for state management. It's basically a simplified observer pattern, with just 1 single observer/listener.

Right now I have this and it worked well:

public final class StateStore<S> {
  
  private var currentState: S
  
  public var listener: ((S) -> Void)? = nil
  
  public init(initialState: S) {
    currentState = initialState
  }
  
  func update(_ block: (inout S) -> Void) {
    var nextState = currentState // struct's copy on write
    block(&nextState)
    currentState = nextState
    listener?(currentState)
  }
  
}

However, I would like to change it to protocol instead of block. Something like:

public protocol StateListener: AnyObject {
  associatedtype S
  func didUpdateState(_ state: S)
}

public final class StateStore<S> {
  ...
  public weak var listener: StateListener<S>? // <- how to deal with associate type 
  ... 
}

I am not able to do so because in the above S is associate type, not generic type. So I got error saying Cannot specialize non-generic type 'StateListener'

I have looked at this but not helpful: Using generic protocols in generic classes

Upvotes: 0

Views: 867

Answers (2)

Sweeper
Sweeper

Reputation: 274885

The key is to parameterise StateStore with a Listener type, rather than an S type. S can then be defined as a type alias of Listener.S:

public final class StateStore<Listener: StateListener> {
    public typealias S = Listener.S

Notice that now you can't have an variable of type StateStore<StateListener>s, that can store all kinds of StateStores with different implementations of StateListeners. The type StateStore<StateListener> doesn't really make sense, and for good reason too.

I would say that wanting to make this into a protocol is rather weird. If you want to give the (S) -> Void type a name, use a type alias inside StateStore:

public typealias StateListener = ((S) -> Void)

Upvotes: 0

user652038
user652038

Reputation:

S is not the Swift style; using full words is the modern standard.

public protocol StateListener: AnyObject {
  associatedtype State
  func didUpdateState(_: State)
}

public final class StateStore<Listener: StateListener> {
  public weak var listener: Listener?
}

You don't need to manually account for the associated type. It's built in, accessible as a nested type:

  func ƒ(_: Listener.State) {

  }

Upvotes: 1

Related Questions