Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Swift: Array of equatable generic type

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:

  • I have a class class Store<S: State> where State is a simple protocol which extends Equatable protocol State: Equatable.
  • I have a class 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

like image 382
Francisco Durdin Garcia Avatar asked Oct 29 '22 20:10

Francisco Durdin Garcia


1 Answers

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 )}
like image 97
dr_barto Avatar answered Nov 15 '22 05:11

dr_barto