0

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

  • Why would you want to make this using a protocol? It seems less flexible to me... – Sweeper Mar 23 '21 at 07:32
  • @Sweeper because the listener block is typically huge, so i ended up with doing `store.listener = { weakSelf?.aHugeFunction() }` all the time –  Mar 23 '21 at 07:37

2 Answers2

1

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) {

  }
0

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)
Sweeper
  • 213,210
  • 22
  • 193
  • 313