DispatchStore 0.5

DispatchStore 0.5

测试已测试
语言语言 SwiftSwift
许可证 MIT
发布日期上次发布2017年8月
SwiftSwift 版本3.1
SPM支持 SPM

Maintained by Alex Usbergo.




Dispatch

Dispatch

Dispatch 是一个轻量级的、基于操作、多存储 Flux 实现的 Swift 库。

安装

如果您使用的是 CocoaPods

将以下代码添加到您的 Podfile

pod 'DispatchStore'

如果您使用的是 Carthage

要安装 Carthage,运行以下代码(使用 Homebrew):

$ brew update
$ brew install carthage

然后,将以下行添加到您的 Cartfile

github "alexdrone/Dispatch" "master"    

概述

Dispatch 是 Swift 中的单向数据流架构Flux 类似实现。Flux 应用程序有三个主要部分:分发器、存储和视图。

这些不应与模型-视图-控制器混淆。在 Dispatch/Flux 应用程序中确实存在控制器,但它们是控制器视图——通常位于顶层,从存储中检索数据,并将此数据传递给子视图(视图)。

Dispatch 放弃 MVC,转而采用单向数据流。当用户与视图交互时,视图通过中央分发器传播一个操作,到处理应用程序数据和业务逻辑的各种存储中,更新所有受影响的视图。

这特别适合 Render 的声明式编程风格,它允许存储发送更新,而不需要指定视图状态之间的转换方式。

  • 存储:保存应用程序的状态。您可以为应用程序的不同领域拥有多个存储。
  • 操作:您只能通过操作执行状态更改。操作是描述状态更改的小数据片段(通常是枚举)。通过大幅度限制状态可以被修改的方式,您的应用程序变得更容易理解,并且更容易与多个协作者一起工作。
  • 分发器:向响应操作的存储发送操作。
  • 视图:您状态的一个简单函数。这特别适合 Render 的声明式编程风格。

Diagram

单一分发器

调度器是应用中管理所有数据流的核心枢纽。本质上,它是一个回调函数的注册列表,没有自己的智能——它是一个简单的将动作分发到存储的机制。每个存储都注册自己并提供一个回调。当动作创建者向调度器提供一个新动作时,应用中的所有存储都通过注册表中的回调接收动作,并将其重定向到各自的reducer。

随着应用的增长,调度器的地位变得更加重要,因为它可以用来以特定的顺序调用注册的回调来管理存储之间的依赖关系。存储可以声明式地等待其他存储完成更新,然后相应地更新自己。

调度器可以在四种不同的模式下运行动作:asyncsyncserialmainThread

此外,可以链式调用 dispatch 方法的尾闭包来执行一些顺序的动作。

存储

存储包含应用状态和逻辑。它们的作用与传统Model-View-Controller(MVC)中的模型类似,但它们管理许多对象的状态——它们不表示像ORM模型那样的单个数据记录。存储不仅仅是管理ORM风格的对象的集合,还用于管理应用中特定领域的应用状态。

如上所述,存储将在调度器中注册自己。存储拥有一个Reducer,它通常基于动作的类型有switch语句——reducer是框架提供的一个唯一开放的类,该库的使用者预期将子类化它,为存储处理的每个动作返回一个操作。

这允许动作通过调度器更新存储的状态。存储更新后,它们通知观察者状态已更改,因此视图可以查询新状态并更新自己。

Y NO Redux 实现吗?

Redux可以被视为一种特殊的Dispatch用例。你可以通过将单个存储注册到调度器并确保存储中状态的不变性来重新创建Redux配置。

入门

让我们在Dispatch中实现一个计数器应用。

首先我们需要一个Counter状态和一些相关的动作。

struct Counter: ModelType {

  let count: Int

  init() {
    self.count = 0
  }

  init(count: Int) {
    self.count = count
  }
  
  // In this example we are implementing Counter as an immutable state, but Dispatch is
  // not opinionated about state immutability.
  // We could have 'count' as a var and simply change its value in the reducer.
  func byAdding(value: Int) -> Counter {
    return Counter(count: self.count + value)
  }

  enum Action: ActionType {
    case increase
    case decrease
    case add(amount: Int)
    case remove(amount: Int)
  }
}

现在我们需要一个Reducer来实现Counter.Action中定义的动作的业务逻辑。reducer需要改变状态(状态由Store拥有)并且为同步地做到这一点,我们使用updateState(closure:)函数。

class CounterReducer: Reducer<Counter, Counter.Action> {

  override func operation(for action: Counter.Action, 
                          in store: Store<Counter, Counter.Action>) -> ActionOperation<Counter, Counter.Action> {

    switch action {

    case .increase:
      return ActionOperation(action: action, store: store) { operation, _, store in
        store.updateModel { model in
          // In this example we are implementing our state as an immutable state (a la Redux) - but 
          // 'Dispatch' is not opinionated about it.
          // We could simply mutate our state by simply doing 'state.count += 1'. 
          // State immutability is a trade-off left to the user of this library.
          model = model.byAdding(value: 1)
        }
        operation.finish()
      }

    case .decrease:
      return ActionOperation(action: action, store: store) { operation, _, store in
        store.updateModel { model in model = model.byAdding(value: -1) }
        operation.finish()
      }

      ...
    }
  }
}

现在让我们看看如何使用我们新定义的Reducer实例化一个Store以及如何将其注册到默认的Dispatcher

let store = Store<Counter, Counter.Action>(identifier: "counter", reducer: CounterReducer())
Dispatcher.default.register(store: store)

发射一个动作就像调用一样简单:

Dispatcher.default.dispatch(Counter.Action.increase)

任何对象都可以通过调用register(observer:callback:)将自身注册为特定存储的观察者。

store.register(observer: self) { model, _ in
  print(model)
}

将所有存储暴露为Dispatcher中的合成getter以获得类型安全的引用是一种方便的方式。这样,你就有一个集中唯一的入口点来访问所有存储。

extension Dispatcher {
  var counterStore: Store<Counter, Counter.Action> {
    return self.store(with: "counter") as? Store<Counter, Counter.Action>
  }
}

高级使用

分散功能利用了操作操作队列,并允许您定义将要运行在您的商店中的操作之间的复杂依赖关系。

同时,也提供了中间件支持,允许您快速将一些面向方面的特性添加到您的设计中。

中间件

任何符合Middleware协议的对象都可以注册到Dispatcher。这为从发送动作到动作到达reducer的时刻提供了一个第三方扩展点。您可以使用中间件进行日志记录、崩溃报告、与异步API通信、路由等。

protocol Middleware {
  func willDispatch(transaction: String, action: AnyAction, in store: AnyStore)
  func didDispatch(transaction: String, action: AnyAction, in store: AnyStore)
}

class Logger: Middleware { ... }

通过调用register(middleware:)来注册您的中间件。

Dispatcher.default.register(middleware: LoggerMiddleware())

记录器

记录器中间件可能是分散功能中最为有趣的功能之一。

通过调用注册您的记录器

Dispatcher.default.register(middleware: RecorderMiddleware())

哇,如果你你的状态是不可变的,只需按下⌘+P即可导航返回到过去的状态,再按⌘+N再次前进。在演示中试一试!

Recorder

链式动作

您可以通过使用dispatch方法的尾部闭包来确保一个动作在另一个动作之后立即发送。

Dispatcher.default.dispatch(action: Action.foo) {
  Dispatcher.default.dispatch(action: Action.bar)
}

同样,您可以通过连续发送这两个动作来达到相同的效果。

Dispatcher.default.dispatch(action: Action.foo, mode: .serial)
Dispatcher.default.dispatch(action: Action.bar, mode: .serial)

此外,使用.sync调用dispatch也会产生相同的效果,但它将阻塞当前正在发送动作的线程,直到操作完成 - 因此,请确保只在非主线程上以.sync模式发送您的动作。

与渲染一起使用

在这个模型中,视图只是状态的简单函数。这特别适合与Render的声明性编程风格一起使用。

查看示例,了解如何充分利用

贡献者