Cycler 3.0.1

Cycler 3.0.1

muukiimuukii 维护。



 
依赖
RxSwift~> 4.2.0
RxCocoa~> 4.2.0
 

Cycler 3.0.1

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(...)

基本演示