Cycler
本说明正在完善中
什么是 Cycler?
这ViewModel层的一个想法。
它主要受Flux架构的启发。
CyclerType
CyclerType 只定义了干净的数据流。
因此,我们自由地使用Cycler。
使用方式之一,CyclerType 适用于 MVVM 架构的 ViewModel。
具有可观察的状态
通过接收Mutation或Action更新状态
接收 Mutation 作为 Commit
接收 Action 作为 Dispatch
通过接收突变或动作发射活动
有时,有一些事件不需要通过动作或突变存储到状态中。所以我们称它们为活动
。
协议
public protocol CyclerType {
associatedtype State
associatedtype Activity
var state: Storage<State> { get }
}
扩展方法
extension CyclerType {
public var activity: Signal<Activity>
public func commit(
_ name: String = "",
_ description: String = "",
file: StaticString = #file,
function: StaticString = #function,
line: UInt = #line,
_ mutate: @escaping (MutableStorage<State>) throws -> Void
) rethrows
public func dispatch<T>(
_ name: String = "",
_ description: String = "",
file: StaticString = #file,
function: StaticString = #function,
line: UInt = #line,
_ action: (CyclerWeakContext<Self>) throws -> T
) rethrows -> T
}
存储
这是用于当前状态的存储。
public class Storage<T> {
public var value: T { get }
public func add(subscriber: @escaping (T) -> Void) -> Token
public func remove(subscriber: Token)
}
public final class MutableStorage<T> : Storage<T> {
public func replace(_ value: T)
public func batchUpdate(_ update: (MutableStorage<T>) -> Void)
public func update<E>(_ value: E, _ keyPath: WritableKeyPath<T, E>)
public func updateIfChanged<E>(_ value: E, _ keyPath: WritableKeyPath<T, E>, comparer: (E, E) -> Bool)
public func asStorage() -> Storage<T>
}
无需RxSwift即可使用
我们可以将存储
单独使用。
记录
我们可以记录有关轮询器的日志。
- 日志包括
- 状态变更
- 接收突变
- 接收动作
- 发射活动
public protocol MutableStorageLogging {
func didChange(value: Any, for keyPath: AnyKeyPath, root: Any)
func didReplace(root: Any)
}
public protocol CycleLogging : MutableStorageLogging {
func didEmit(activity: Any, file: StaticString, function: StaticString, line: UInt, on cycler: AnyCyclerType)
func willDispatch(name: String, description: String, file: StaticString, function: StaticString, line: UInt, on cycler: AnyCyclerType)
func willMutate(name: String, description: String, file: StaticString, function: StaticString, line: UInt, on cycler: AnyCyclerType)
func didMutate(name: String, description: String, file: StaticString, function: StaticString, line: UInt, on cycler: AnyCyclerType)
}
用法
实际代码
定义ViewModel
import Cycler
class ViewModel : CyclerType {
enum Activity {
case didReachBigNumber
}
struct State {
// - Data
fileprivate var count: Int = 0
// - Computed
// It will be subscribed whole of this State, so, we can use computed property.
var countText: String {
return count.description
}
}
private let disposeBag = DisposeBag()
let state: Storage<State> = .init(.init(count: 0))
init() {
}
func increment(number: Int) {
// Dispatch Action
// Action can contain async operation.
dispatch("increment") { (context) in
// Context references self weakly.
Observable.just(())
.delay(0.1, scheduler: MainScheduler.instance)
.do(onNext: {
// Retain references of context
// So, run operation completely in this scope.
context.retain { c in
// Mutation
// Transaction for mutating.
c.commit { (state) in
// State is MutableStorage.
state.updateIfChanged(state.value.count + number, \.count)
}
if c.currentState.count > 10 {
// Emit Activity.
// Activity just an event that does not need to store to State.
c.emit(.didReachBigNumber)
}
}
})
.subscribe()
}
.disposed(by: disposeBag)
}
func decrement(number: Int) {
dispatch("decrement") { _ in
commit { (state) in
state.updateIfChanged(state.value.count - number, \.count)
}
}
}
}
订阅状态
let viewModel = ViewModel()
// Subscribe one property of the State.
viewModel
.state
.asObservable()
.map { $0.countText }
.distinctUntilChanged()
// Or, subscribe one property of the State by KeyPath.
viewModel
.state
.asObservable(keyPath: \.countText)
.distinctUntilChanged()
// Or,
viewModel
.state
.changed(\.countText) // This includes `distinctUntilChanged`
distinctUntilChanged 非常重要。
变异会变异整个状态。来自状态的可观察对象会在更新状态时发送事件。这种行为将导致不必要的操作。
订阅活动
// Subscribe activity.
viewModel
.activity
.emit(...)