Unicore 3.3.0

Unicore 3.3.0

Maxim Bazarov 维护。



Unicore 3.3.0

  • Maxim Bazarov

Build Status Cocoapods Carthage compatible License Platform

Unicore The Unicore

架构 | 安装 | 框架 API | 常见问题解答 | 示例 | 致谢

iOS: 9.0 + | macOS: 10.10 + | watchOS 2.0 + | tvOS: 9.0 +


The Unicore 是一种高度可扩展的 应用程序设计方法(架构),可以使您提高应用程序的可维护性、提高可测试性,并通过解耦应用程序代码为您的团队提供灵活性。

它是 数据驱动组件单向数据流 的便捷组合。

该框架本身为您提供方便的方式来管理应用程序状态。它在下面的图片中代表 Core

Unicore

  • Reducer 是一个纯函数 (State, Action) -> State
  • Core 是一个与 Redux 的类似 State 存储器,它允许您向其中派遣 Action观察 状态变化
  • Connector 是一个纯函数 (State) -> Props,其中 Props 是组件执行其工作所需的结构
  • Component 是一个 Props 消费者(屏幕、API 服务、Core Location 管理器等...)它提供副作用

架构

数据驱动组件

待定

单向数据流

Unicore背后的理念是拥有一个唯一的
(应用状态)源,以单向方式更改。

应用状态

应用状态是一个简单的结构。例如,简单的结构如下

struct AppState {
    let counter: Int
    let step: Int

    // Initial state
    static let initial = AppState(counter: 0, step: 1)
}

设想一个简单的应用,我们需要显示计数器以及增加/减少按钮。让我们再添加一点逻辑,也让我们控制每一步。

在这种情况下,AppState将是我们唯一的当前应用状态的来源,我们查看AppState实例,得到我们需要显示的正确值。这就是唯一真相源

但问题是,要为应用中的每个部分(屏幕,服务等)提供对这个状态的控制,更重要的是,提供一种使所有人都能知道状态已更改的方式来修改这个状态。观察者模式将解决这些问题,但为了修改,我们需要一些外部方式,而不仅仅是观察者中的内部方式。

这就是为什么我们使用事件总线模式来解决这个问题,并派发操作到内核,内核会更改状态。

内核

内核是动作的调度器,它使用底层序列队列,所以一次只处理一个动作,这就是为什么阻止reducer函数如此重要的原因。内核是一个泛型类型,因此我们可以为任何状态创建它。

let core = Core<AppState>( // #1
    state: AppState.initial, // #2
    reducer: reduce // #3
)
  1. 将泛型参数设置为AppState,让内核知道我们需要一个处理AppState状态的reducer。
  2. 为内核提供初始状态
  3. 为内核提供reducer,我们之前编写的函数。

当您向内核派发操作时,它使用Reducer创建应用状态的新的版本,并向所有订阅者传达有一个新的应用状态。

操作

操作也是遵循Action协议的简单结构。

动作的名称描述了发生了什么,动作的字段(负载)描述了事件的详细信息。例如这个动作:

struct StepChangeRequested: Action {
    let step: Int
}

表示请求了步骤改变,并且请求的新步骤等于字段 step

一些动作可能不包含字段,它们带来的唯一信息就是动作的名称。

struct CounterIncreaseRequested: Action {}
struct CounterDecreaseRequested: Action {}

这些动作给我们提供信息,请求了计数器的增加或减少。

有了当前状态和动作,我们可以使用 Reducer 来获取新状态。

Reducer

Reducer 是一个函数,它接收一个状态和一个动作作为参数,并返回一个新状态,它是一个纯函数,并且它必须不阻塞当前线程,这意味着所有重计算都不应在 Reducers 中进行。

Reducer 是唯一更改当前状态的方式,例如,如果动作是 StepChangeRequested,那么我们使用负载值更新步骤

func reduce(_ old: AppState, with action: Action) -> AppState {
    switch action {

    case let payload as StepChangeRequested: // #1
        return AppState( // # 2
            counter: old.counter, // #3
            step: payload.step // #4
        )

    default:
        return old // #5
    }
}
  1. 如果动作是 StepChangeRequested,则展开 payload
  2. 返回新的 AppState 实例
  3. counter 值保持不变
  4. step 使用来自 payload 的新值更新
  5. 对于所有其他动作,返回旧状态

此 Reducer 的测试可能如下所示

func testReducer_StepChangeRequestedAction_stepMustBeUpdated() {
    let sut = AppState(counter: 0, step: 1)
    let action = StepChangeRequested(step: 7)
    let new = reduce(sut, with: action)
    XCTAssertEqual(new.step, 7)
}

让我们也添加增加和减少动作的处理程序

func reduce(_ old: AppState, with action: Action) -> AppState {
    switch action {

    case let payload as StepChangeRequested:
        return AppState(
            counter: old.counter,
            step: payload.step
        )

    case is CounterIncreaseRequested:
        return AppState(
            counter: old.counter + old.step, // #1
            step: old.step
        )

    case is CounterDecreaseRequested:
        return AppState(
            counter: old.counter - old.step,
            step: old.step
        )

    default:
        return old
    }
}
  1. 我们计算 counter 的新值

测试看起来可能像这样

func testReducer_CounterIncreaseRequested_counterMustBeIncreased() {
    let sut = AppState(counter: 0, step: 3)
    let action = CounterIncreaseRequested()
    let new = reduce(sut, with: action)
    XCTAssertEqual(new.counter, 3)
}

func testReducer_CounterDecreaseRequested_counterMustBeDecreased() {
    let sut = AppState(counter: 0, step: 3)
    let action = CounterDecreaseRequested()
    let new = reduce(sut, with: action)
    XCTAssertEqual(new.counter, -3)
}

好吧,我们准备好了使应用程序运行所需的一切,唯一需要做的就是创建我们的 Core 实例

安装

Unicore 通过 CocoaPodsCarthage 提供。要安装它,只需向 Podfile 添加以下行:

CocoaPods

将以下行添加到你的 Podfile 文件中

pod 'Unicore', '~> 1.0.2'

Carthage

或在以下行中添加到您的Cartfile

github "Unicore/Unicore"

API和用法

  • 动作
  • 核心
    • observe(on:with:)
    • dispatch(_ action:)
    • onAction(execute:)
  • DisposerDisposable
    • dispose(on:)

核心

要使用Unicore,您必须创建一个Core类的实例。由于Core是一个泛型类型,在创建实例之前,您必须定义State类,它可以是您想要的任何类型。假设我们有一个状态描述为App状态的结构,那么您需要描述如何使用一个Reducer来使此状态对动作做出反应,现在您就可以创建Core的实例了

let core = Core<AppState>(state: AppState.initial, reducer: reducer)

observe(on:with:)

获取当前状态的唯一方法是订阅状态变化,非常重要,当您订阅时,您将立即收到当前状态值

core.observe { state in
    // do something with state
    print(state.counter)
}.dispose(on: disposer) // dispose the subscription when current disposer will dispose

每当状态更新时,都会调用这个闭包。

如果您想在特定线程上处理状态更新,例如在主线程上更新您的屏幕,可以使用observe(on: DispatchQueue)语法

core.observe(on: .main) { (state) in
    self.counterLabel.text = String(state.counter)
}.dispose(on: disposer)

dispatch(_ action:)

let action = CounterIncreaseRequested() // #1
core.dispatch(action) // #2

onAction(execute:)

订阅观察动作和旧状态,动作发生更改之前的状态。建议仅用于调试目的。

处置

当你订阅状态更改时,函数 observe 返回一个 PlainCommand 来在不再需要时移除订阅。你可以直接在需要取消订阅时调用它。

class YourClass {
    let unsubscribe: PlainCommand?

    func connect(to core: Core<AppState>) {
        unsubscribe = core.observe { (state) in
            // handle the state
        }
    }

    deinit {
        unsubscribe?()
    }
}

或者你可以使用一个 Disposer 并将此命令添加到其中。在它将处置时,disposer 将会调用这个命令。

class YourClass {
    let disposer = Disposer()

    func connect(to core: Core<AppState>) {
        core.observe { (state) in
            // handle the state
        }.dispose(on: disposer)
        // when YourClass will deinit hence disposer will also deinited, and before that, it will call all unsubscription functions registered in it
    }
}

示例

TheMovieDB.org 客户端(开发中)

致谢

Maxim Bazarov:该框架的维护者,是该方法的传教士。

Alexey Demedetskiy:原始 swift 版本的作者以及众多示例。

Redux JS:原始理念。

许可证

Unicore 在 MIT 许可下可用。有关更多信息,请参阅 LICENSE 文件。