The Unicore
架构 | 安装 | 框架 API | 常见问题解答 | 示例 | 致谢
iOS: 9.0 + | macOS: 10.10 + | watchOS 2.0 + | tvOS: 9.0 +
The Unicore 是一种高度可扩展的 应用程序设计方法(架构),可以使您提高应用程序的可维护性、提高可测试性,并通过解耦应用程序代码为您的团队提供灵活性。
它是 数据驱动组件 和 单向数据流 的便捷组合。
该框架本身为您提供方便的方式来管理应用程序状态。它在下面的图片中代表 Core
。
- 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
)
- 将泛型参数设置为
AppState
,让内核
知道我们需要一个处理AppState
状态的reducer。 - 为内核提供初始状态
- 为内核提供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
}
}
- 如果动作是
StepChangeRequested
,则展开payload
- 返回新的
AppState
实例 counter
值保持不变step
使用来自payload
的新值更新- 对于所有其他动作,返回旧状态
此 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
}
}
- 我们计算
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 通过 CocoaPods 和 Carthage 提供。要安装它,只需向 Podfile 添加以下行:
CocoaPods
将以下行添加到你的 Podfile 文件中
pod 'Unicore', '~> 1.0.2'
Carthage
或在以下行中添加到您的Cartfile
github "Unicore/Unicore"
API和用法
动作
核心
observe(on:with:)
dispatch(_ action:)
onAction(execute:)
Disposer
和Disposable
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 文件。