Sources/Fluxor/Selector.swift
/*
* Fluxor
* Copyright (c) Morten Bjerg Gregersen 2020
* MIT license, see LICENSE file for details
*/
import Foundation
// swiftlint:disable function_parameter_count
/// Something which selects a `Value` from the specified `State`.
public protocol SelectorProtocol {
/// The input for the `Selector`.
associatedtype State
/// The output of the `Selector`,
associatedtype Value
/**
A pure function which takes a `State` and returns a `Value` from it.
- Parameter state: The `State` to map
- Returns: The `Value` mapped from the `State`
*/
func map(_ state: State) -> Value
}
/**
A type which takes a `State` and returns a `Value` from it.
`Selector`s can be based on other `Selector`s making it possible to select a combined `Value`.
*/
public class Selector<State, Value>: SelectorProtocol {
/// An unique identifier used when overriding the `Selector` on the `MockStore`.
public let id = UUID()
/// The closue used for the mapping.
private let _projector: (State) -> Value
/// The latest value for a state hash.
internal private(set) var result: (stateHash: UUID, value: Value)?
/**
Creates a `Selector` from a `keyPath`.
- Parameter keyPath: The `keyPath` to create the `Selector` from
*/
public convenience init(keyPath: KeyPath<State, Value>) {
self.init(projector: { $0[keyPath: keyPath] })
}
/**
Creates a `Selector` from a `projector` closure.
- Parameter projector: The `projector` closure to create the `Selector` from
*/
public init(projector: @escaping (State) -> Value) {
_projector = projector
}
public func map(_ state: State) -> Value {
_projector(state)
}
}
/// A `Selector` created from a `Selector`s and a `projector` function.
public class Selector1<State, S1, Value>: Selector<State, Value> where
S1: SelectorProtocol, S1.State == State {
/// A pure function which takes the `Value` from the other `Selector` and returns a new `Value`.
public let projector: (S1.Value) -> Value
/**
Creates a `Selector` from a `Selector` and a `projector` function.
- Parameter selector1: The first `Selector`
- Parameter projector: The closure to pass the value from the `Selector` to
*/
public init(_ selector1: S1, _ projector: @escaping (S1.Value) -> Value) {
self.projector = projector
super.init(projector: { projector(selector1.map($0)) })
}
/**
Creates a `Selector` from a `Selector` and a `projector` function.
- Parameter selector1: The first `Selector`
- Parameter projector: The closure to pass the value from the `Selector` to
*/
public convenience init(_ selector1: S1, keyPath: KeyPath<S1.Value, Value>) {
self.init(selector1) { $0[keyPath: keyPath] }
}
}
/// A `Selector` created from two `Selector`s and a `projector` function.
public class Selector2<State, S1, S2, Value>: Selector<State, Value> where
S1: SelectorProtocol, S1.State == State,
S2: SelectorProtocol, S2.State == State {
/// A pure function which takes the `Value`s from the other `Selector`s and returns a new `Value`.
public let projector: (S1.Value, S2.Value) -> Value
/**
Creates a `Selector` from two `Selector`s and a `projector` function.
- Parameter selector1: The first `Selector`
- Parameter selector2: The second `Selector`
- Parameter projector: The closure to pass the values from the `Selector`s to
*/
public init(_ selector1: S1,
_ selector2: S2,
_ projector: @escaping (S1.Value, S2.Value) -> Value) {
self.projector = projector
super.init(projector: { projector(selector1.map($0), selector2.map($0)) })
}
}
/// A `Selector` created from three `Selector`s and a `projector` function.
public class Selector3<State, S1, S2, S3, Value>: Selector<State, Value> where
S1: SelectorProtocol, S1.State == State,
S2: SelectorProtocol, S2.State == State,
S3: SelectorProtocol, S3.State == State {
/// A pure function which takes the `Value`s from the other `Selector`s and returns a new `Value`.
public let projector: (S1.Value, S2.Value, S3.Value) -> Value
/**
Creates a `Selector` from three `Selector`s and a `projector` function.
- Parameter selector1: The first `Selector`
- Parameter selector2: The second `Selector`
- Parameter selector3: The third `Selector`
- Parameter projector: The closure to pass the values from the `Selectors` to
*/
public init(_ selector1: S1,
_ selector2: S2,
_ selector3: S3,
_ projector: @escaping (S1.Value, S2.Value, S3.Value) -> Value) {
self.projector = projector
super.init(projector: { projector(selector1.map($0),
selector2.map($0),
selector3.map($0)) })
}
}
/// A `Selector` created from four `Selector`s and a `projector` function.
public class Selector4<State, S1, S2, S3, S4, Value>: Selector<State, Value> where
S1: SelectorProtocol, S1.State == State,
S2: SelectorProtocol, S2.State == State,
S3: SelectorProtocol, S3.State == State,
S4: SelectorProtocol, S4.State == State {
/// A pure function which takes the `Value`s from the other `Selector`s and returns a new `Value`.
public let projector: (S1.Value, S2.Value, S3.Value, S4.Value) -> Value
/**
Creates a `Selector` from four `Selector`s and a `projector` function.
- Parameter selector1: The first `Selector`
- Parameter selector2: The second `Selector`
- Parameter selector3: The third `Selector`
- Parameter selector4: The fourth `Selector`
- Parameter projector: The closure to pass the values from the `Selectors` to
*/
public init(_ selector1: S1,
_ selector2: S2,
_ selector3: S3,
_ selector4: S4,
_ projector: @escaping (S1.Value, S2.Value, S3.Value, S4.Value) -> Value) {
self.projector = projector
super.init(projector: { projector(selector1.map($0),
selector2.map($0),
selector3.map($0),
selector4.map($0)) })
}
}
/// A `Selector` created from five `Selector`s and a `projector` function.
public class Selector5<State, S1, S2, S3, S4, S5, Value>: Selector<State, Value> where
S1: SelectorProtocol, S1.State == State,
S2: SelectorProtocol, S2.State == State,
S3: SelectorProtocol, S3.State == State,
S4: SelectorProtocol, S4.State == State,
S5: SelectorProtocol, S5.State == State {
/// A pure function which takes the `Value`s from the other `Selector`s and returns a new `Value`.
public let projector: (S1.Value, S2.Value, S3.Value, S4.Value, S5.Value) -> Value
/**
Creates a `Selector` from five `Selector`s and a `projector` function.
- Parameter selector1: The first `Selector`
- Parameter selector2: The second `Selector`
- Parameter selector3: The third `Selector`
- Parameter selector4: The fourth `Selector`
- Parameter selector5: The fifth `Selector`
- Parameter projector: The closure to pass the values from the `Selectors` to
*/
public init(_ selector1: S1,
_ selector2: S2,
_ selector3: S3,
_ selector4: S4,
_ selector5: S5,
_ projector: @escaping (S1.Value, S2.Value, S3.Value, S4.Value, S5.Value) -> Value) {
self.projector = projector
super.init(projector: { projector(selector1.map($0),
selector2.map($0),
selector3.map($0),
selector4.map($0),
selector5.map($0)) })
}
}
/// Creator functions.
public extension Selector {
/**
Creates a `Selector` from a `Selector` and a `projector` function.
- Parameter selector1: The first `Selector`
- Parameter projector: The closure to pass the value from the `Selector` to
- Returns: A `Selector` from the given `Selector` and the `projector` function
*/
static func with<S1>(_ selector1: S1,
projector: @escaping (S1.Value) -> Value)
-> Selector1<State, S1, Value> {
.init(selector1, projector)
}
/**
Creates a `Selector` from a `Selector` and a `KeyPath`.
- Parameter selector1: The first `Selector`
- Parameter keyPath: The `keyPath` to create the `Selector` from
- Parameter keyPath: The `KeyPath` to subscript in the value from the `Selector`
- Returns: A `Selector` from the given `Selector` and the `KeyPath`
*/
static func with<S1>(_ selector1: S1,
keyPath: KeyPath<S1.Value, Value>)
-> Selector1<State, S1, Value> {
.init(selector1, keyPath: keyPath)
}
/**
Creates a `Selector` from two `Selector`s and a `projector` function.
- Parameter selector1: The first `Selector`
- Parameter selector2: The second `Selector`
- Parameter projector: The closure to pass the values from the `Selector`s to
- Returns: A `Selector` from the given `Selector`s and the `projector` function
*/
static func with<S1, S2>(_ selector1: S1,
_ selector2: S2,
projector: @escaping (S1.Value, S2.Value) -> Value)
-> Selector2<State, S1, S2, Value> {
.init(selector1, selector2, projector)
}
/**
Creates a `Selector` from three `Selector`s and a `projector` function.
- Parameter selector1: The first `Selector`
- Parameter selector2: The second `Selector`
- Parameter selector3: The third `Selector`
- Parameter projector: The closure to pass the values from the `Selectors` to
- Returns: A `Selector` from the given `Selector`s and the `projector` function
*/
static func with<S1, S2, S3>(_ selector1: S1,
_ selector2: S2,
_ selector3: S3,
projector: @escaping (S1.Value, S2.Value, S3.Value) -> Value)
-> Selector3<State, S1, S2, S3, Value> {
.init(selector1, selector2, selector3, projector)
}
/**
Creates a `Selector` from four `Selector`s and a `projector` function.
- Parameter selector1: The first `Selector`
- Parameter selector2: The second `Selector`
- Parameter selector3: The third `Selector`
- Parameter selector4: The fourth `Selector`
- Parameter projector: The closure to pass the values from the `Selectors` to
- Returns: A `Selector` from the given `Selector`s and the `projector` function
*/
static func with<S1, S2, S3, S4>(_ selector1: S1,
_ selector2: S2,
_ selector3: S3,
_ selector4: S4,
projector: @escaping (S1.Value, S2.Value, S3.Value, S4.Value) -> Value)
-> Selector4<State, S1, S2, S3, S4, Value> {
.init(selector1, selector2, selector3, selector4, projector)
}
/**
Creates a `Selector` from five `Selector`s and a `projector` function.
- Parameter selector1: The first `Selector`
- Parameter selector2: The second `Selector`
- Parameter selector3: The third `Selector`
- Parameter selector4: The fourth `Selector`
- Parameter selector5: The fifth `Selector`
- Parameter projector: The closure to pass the values from the `Selectors` to
- Returns: A `Selector` from the given `Selector`s and the `projector` function
*/
static func with<S1, S2, S3, S4, S5>(_ selector1: S1,
_ selector2: S2,
_ selector3: S3,
_ selector4: S4,
_ selector5: S5,
projector: @escaping (S1.Value, S2.Value, S3.Value,
S4.Value, S5.Value) -> Value)
-> Selector5<State, S1, S2, S3, S4, S5, Value> {
.init(selector1, selector2, selector3, selector4, selector5, projector)
}
}
/// Memoization support, where the `Selector` remembers the last result to speed up mapping.
internal extension Selector {
/**
Sets the value and the corresponding `stateHash`.
- Parameter value: The value to save
- Parameter stateHash: The hash of the state the value was selected from
*/
func setResult(value: Value, forStateHash stateHash: UUID) {
result = (stateHash: stateHash, value: value)
}
/**
Selects the `Value` from the `State` based on the subclass's `map` function and saves the result.
- If a value is already saved and the saved state hash matches the passed, the saved value is returned.
- If a value is already saved but the saved state hash doesn't match the passed
a new value is selected and saved along with the passed state hash
- Parameter state: The `State` to select from
- Parameter stateHash: The hash of the `State` to select from
- Returns: The `Value` mapped with the `projector`
*/
func map(_ state: State, stateHash: UUID) -> Value {
if let result = result, result.stateHash == stateHash {
return result.value
}
let value = map(state)
setResult(value: value, forStateHash: stateHash)
return value
}
}