提交
This commit is contained in:
340
TUIKit/TUIRoomKit/Source/Common/Basic/Fluxor/Selector.swift
Normal file
340
TUIKit/TUIRoomKit/Source/Common/Basic/Fluxor/Selector.swift
Normal file
@@ -0,0 +1,340 @@
|
||||
/*
|
||||
* 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
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user