0

I built a state management in my ios applications similar to redux in javascript. It work and it's nice to work with, but i want to abstract and decouple some part in a framework for reusability, testing and sharing.

To add some context, the main idea is to create a class store with a getState and dispatch methods. A struct State defined by the user is passed at the store initialization.

/* Store signature: class MyStore<State>: MyStoreProcotol */

let store = MyStore(state: ApplicationState())`

Once the store is initialized, i'm trying to inject it in any class (UIViewController for example, but not mandatory) that conform to ConnectionProtocol. In the code below, I pass the controller and the store to a function injectStoreInView. I'm trying to inject the store in the controller from this function, func injectStoreInView<State>(viewController: UIViewController, store: MyStore<State>)

I've try several approaches with generics, whithout success. I have read on "type erasure" and follow tutorials, but i'm not sure if it help for this case and how to apply it. I have troubles to handle the type of the parameter store of injectStoreInView, and can't assign it later in the controller controller.connection.store.

This is the code, where a lot of parts has been removed to illustrate the problem. (This code does not work.)

Framework part:

import UIKit

/* store */

protocol MyStoreProcotol {

  associatedtype State

  func getState() -> State

}

class MyStore<State>: MyStoreProcotol {

  var state: State

  init(state: State) {
    self.state = state
  }

  func getState() -> State {
    return self.state
  }

}

/* connection */

protocol ConnectionProtocol {

  associatedtype State

  var connection: Connection<State> { get }

}

class Connection<State> {
  var store: MyStore<State>? = nil
}

func injectStoreInView<State>(
  viewController: UIViewController,
  store: MyStore<State>
) {
  if let controller = viewController /* as ConnectionProtocol */ {
    controller.connection.store = store

    print("yeah !")
  }
}

In the application part:

struct ApplicationState {
  var counter = 0
}

class StartViewConnector: UIViewController, ConnectionProtocol {

  let connection = Connection<ApplicationState>()

}

func appDelegatedidFinishLaunchingWithOptions() -> Bool {

  let store = MyStore(state: ApplicationState())

  let controller = StartViewConnector() // connector can be any class who conform to ConnectionProtocol, StartViewConnector for test but self.window?.rootViewController in iOS App

  injectStoreInView(viewController: controller, store: store)

  return true
}

appDelegatedidFinishLaunchingWithOptions()

1 Answers1

0

Following the advice of @Brandon about associatedTypes and protocolInjection solved the problem. The link he provided in the comment was really helpful (How to create generic protocols in Swift?).

This code works:

Framework:

import UIKit

/* store */

protocol MyStoreProcotol {

  associatedtype State

  func getState() -> State

}

class MyStore<State>: MyStoreProcotol {

  var state: State

  init(state: State) {
    self.state = state
  }

  func getState() -> State {
    return self.state
  }

}

/* connection */

protocol Connectable {

  associatedtype Store

  var connection: Connection<Store> { get }

}

protocol ConnectionProtocol {

  associatedtype Store

  var store: Store? { get set }

}

class Connection<Store>: ConnectionProtocol {

  var store: Store? = nil

}

func injectStoreInView
<
  Store: MyStoreProcotol,
  Controller: Connectable
>
(
  viewController: Controller,
  store: Store
) where Controller.Store == Store
{
  viewController.connection.store = store
}

Usage in the applications:

struct ApplicationState {
  var counter = 0
}

class StartViewConnector: UIViewController, Connectable {

  let connection = Connection<MyStore<ApplicationState>>()

}

func appDelegatedidFinishLaunchingWithOptions() -> Bool {

  let store = MyStore(state: ApplicationState())

  let connector = StartViewConnector()

  injectStoreInView(viewController: connector, store: store)

  // Now the store is properly injected, connector.connection.store = store

  return true
}

appDelegatedidFinishLaunchingWithOptions()