I have been the last days fighting with some issues regarding generics on Swift, and I don't find a way to figure out how to achieve this:
class Store<S: State>
where State
is a simple protocol which extends Equatable protocol State: Equatable
.Logger
where I want to store an array of Stores
to keep track of each change on them and compare their State
with their old values, being able to check what changes in each iteration.For this, I need to store an array of Any Kind of Store in my logger class. The problem comes when I try to use a val storeArray = [Store<Any>]
, it doesn't work because Any is not an Equatable
type, and I will need them to extend Equatable
or NSObject
to be able to compare the states between them.
Is possible to achieve this in Swift? Or find out another way to compare 2 items without make a generic extend a Equatable
protocol?
In case you wanna check the implementation :
State :
protocol State: Equatable {
}
Store :
class Store<S: State> {
private var _initialState: S
private var _state: S
private var processor = PublishSubject<S>()
init(initialState: S) {
_initialState = initialState
_state = initialState
}
var state: S {
get {
return _state
}
set(value) {
if (value != state) {
_state = value
processor.onNext(value)
}
}
}
func initialState() -> S {
return _initialState
}
/// Initialize the store. Called after all stores instances are ready.
func initialize() {
//TODO Check if its possible to force an override over a method
preconditionFailure("This method must be overridden")
}
}
After the suggestion of Vadian I have tried to move it to a protocol with an associate type:
protocol Store: class {
associatedtype State : StateDelegate
var processor : PublishSubject<State> { get }
var _state : State { get set }
var state: State { get set }
func initialState() -> State
func flowable() -> Observable<State>
func initialize() -> Void
}
extension Store {
var state: State {
get {
return _state
}
set(value) {
if (value != state) {
_state = value
processor.onNext(value)
}
}
}
func flowable() -> Observable<State> {
return processor.startWith(state)
}
func initialState() -> State {
return State.init()
}
}
But I retrieve the next error when I try to create an [Store]
array:
Protocol 'Store' can only be used as a generic constraint because it has Self or associated type requirements
I think I understand your problem now. How about this solution:
Instead of letting State
conform to Equatable
, you add your own custom equality check to the protocol; this way you can store your states in an array (var states: [State]
). The drawback is that you cannot use generics but instead have to type-check in code, like in the good old times.
For example, a simple version of the State
protocol:
protocol State {
func isEqualTo(_ other: State) -> Bool
}
Your concrete state types have to implement isEqualTo
and perform a type check before testing equality:
struct State1: State {
var foo: String
func isEqualTo(_ other: State) -> Bool {
guard let state1 = other as? State1 else { return false }
return self.foo == state1.foo
}
}
Now you can store your states in an array and e.g. check if a new state already is contained:
let states: [State] = [ State1(foo: "hi"), State2(bar: 42), State1(foo: "bye")]
let newState = State2(bar: 42)
let containsNewState = states.contains { $0.isEqualTo(newState )}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With