Redux in Swift

      Nessun commento su Redux in Swift

Inrtroduzione

Redux è un architettura dove tutto lo stato dell’applicazione vive in un container. L’unico modo di cambiare questo stato è di creare un nuovo stato basato sullo stato corrente e richiederne il cambiamento.

Andiamo ad esaminare i vari layer:

  • Store gestisce gli stati dell applicazione
  • State determina quale view elements mostrare
  • Action e’ un immutable data che descrive il cambiamento di stato (solitamente un enum)
  • Reducer cambia lo stato dell’app utilizzando lo stato corrente e un’action (una funzione che ha come input stato e action e ritorna uno state)

Dopo questa piccola premessa passiamo alla pratica…

Demo

Per la nostra demo andremo a creare una semplice calcolatrice, quindi passiamo alla creazione di un nuovo progetto di tipo single view e installiamo il seguente framework tramite cocoapods:

  • pod ‘ReSwift’

Per maggiori info riguardanti il framework è possibile visionare il seguente link

Creazione dell’interfaccia

La nostra view conterra due text field per gli input, una label per il visualizzare il risultato delle operazione aritmetiche e 4 bottoni per le quattro (addizione, sottrazione, moltiplicazione e divisione)

Implementazione del codice

Per prima cosa, andiamo a creare un nuovo file per dichiarare l’ AppState. Com’è possibile notare dall codice qui sotto, l’Appstate contiene solamente una variabile per il risultato delle operazione aritmetiche.

AppState

import ReSwift
struct AppState: StateType {
    var result = 0.0
}

Action

Come detto precedentemente l’action sono delle operazioni che descrivono un cambiamento di stato in questo caso addizione,sottrazione, moltiplicazione e divisione.

import ReSwift
enum ActionCalculator: Action { 
case addition(firstValue: String?, secondValue: String?) 
case subtraction(firstValue: String?, secondValue: String?) 
case multiplication(firstValue: String?, secondValue: String?) 
case division(firstValue: String?, secondValue: String?)
}

Reducer

Il reducer sostanzialmente è una funzione che prende come input, l’action e lo stato dell’applicazione e ritorna il nuovo stato. In poche parole è qui che viene implementato la logica di bussiness, tutta via per operazioni più complesse si crea un middleware.

import ReSwift
func calculatorReducer(action: Action, state: AppState?) -> AppState {
    var state = state ?? AppState()
    switch action {
    case ActionCalculator.addition(let first, let second):
        state.result = Double.stringToDouble(stringVal: first) + Double.stringToDouble(stringVal: second)
    case ActionCalculator.multiplication(let first, let second):
        state.result = Double.stringToDouble(stringVal: first) * Double.stringToDouble(stringVal: second)
    case ActionCalculator.subtraction(let first, let second):
       state.result =  Double.stringToDouble(stringVal: first) - Double.stringToDouble(stringVal: second)
    case ActionCalculator.division(let first, let second):
       state.result =  Double.stringToDouble(stringVal: first) / Double.stringToDouble(stringVal: second)
    default:
        break
    }
    return state
}

View

In redux il view controller deve essere il più semplice possibile ovvero deve contenere solamente gli elementi grafici e non deve effettuare nessun altra operezione.

 
import UIKit
import ReSwift
let mainStore = Store<AppState>(reducer: calculatorReducer, state: nil)
class ViewController: UIViewController {
    @IBOutlet weak var resultLabel: UILabel!
    @IBOutlet weak var secondValueLabel: UITextField!
    @IBOutlet weak var firstValueLabel: UITextField!
    override func viewDidLoad() {
        super.viewDidLoad()
    }
    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
        mainStore.subscribe(self)
    }
    override func viewWillDisappear(_ animated: Bool) {
        mainStore.unsubscribe(self)
    }
    @IBAction func divide(_ sender: Any) {
        mainStore.dispatch(ActionCalculator.division(firstValue: firstValueLabel.text, secondValue: secondValueLabel.text))
    }
    @IBAction func add(_ sender: Any) {
        mainStore.dispatch(ActionCalculator.addition(firstValue: firstValueLabel.text, secondValue: secondValueLabel.text))
    }
}
extension ViewController: StoreSubscriber {
    func newState(state: AppState){
        DispatchQueue.main.async { [weak self] in
            guard let self = self else {return}
            self.resultLabel.text = "\(state.result)"
        }
    }
}

Il codice completo dell’ applicazione è disponibile su github.