Vuex 和 Flux
Swift 的单向状态管理架构——灵感来自介绍
VueFlux 是 Swift 的 Architecture,用于以单向数据流管理状态,灵感来自 Vuex 和 Flux.
VueFlux 支持多 store,因此所有 ViewControllers 都有指定的 store,规则的规则确保状态只能以可预测的方式突变。
store 还可以接收全局分发的 action。
这使得 ViewControllers 之间的依赖得到释放。此外,应用程序中的共享状态也可以通过共享的 store 实例来支持。
尽管 VueFlux 使您的项目更具有生产力和代码更易于阅读,但它也带来了更多概念和模板的成本。
如果您的项目是小型的,您可能不需要 VueFlux。
但是,随着项目规模的扩大,VueFlux 将成为处理复杂数据流的最佳选择。
VueFlux 通过高效的响应式系统接收状态变化。 VueFluxReactive 是 µ 响应式框架,兼容此架构。
任意第三方响应式框架(例如 RxSwift,ReactiveSwift,等)也可以与 VueFlux 一起使用。
关于 VueFlux
VueFlux 通过显式划分构成 ViewController 的角色来实现单向和可预测的流程。它由以下核心概念组成。
ViewController 使用响应式系统观察状态变化。
示例代码使用将要介绍的 VueFluxReactive。
您可以在这里看到示例实现。
状态
这是一个仅用于约束动作和突变类型的协议,表示存储器管理的状态。
实施一些状态的属性,并通过 fileprivate 访问控制使其只读,如下所示。
将由突变修改,并且属性将由计算属性发布。
final class CounterState: State {
typealias Action = CounterAction
typealias Mutations = CounterMutations
fileprivate let count = Variable(0)
}
动作
这是分发动作函数的代理。
它们可以执行异步操作,如请求后端API。
动作函数发出的动作的类型由状态决定。
enum CounterAction {
case increment, decrement
}
extension Actions where State == CounterState {
func increment() {
dispatch(action: .increment)
}
func decrement() {
dispatch(action: .decrement)
}
}
突变
这是一个表示用于修改状态的 commit 函数的协议。
可以在同一文件中实现它,以更改状态的 fileprivate 属性。
实际上更改 Store 中的状态的唯一方式是通过突变提交动作。
状态的变化必须同步执行。
struct CounterMutations: Mutations {
func commit(action: CounterAction, state: CounterState) {
switch action {
case .increment:
state.count.value += 1
case .decrement:
state.count.value -= 1
}
}
}
计算属性
这是发布状态只读属性的代理。
可以在同一文件中实现它,以访问和发布状态的 fileprivate 属性。
Store 中的状态属性只能通过这种方式访问。
extension Computed where State == CounterState {
var countTextValues: Signal<String> {
return state.count.signal.map { String($0) }
}
}
存储
存储器管理状态,而且还可以通过共享存储实例管理应用程序中的共享状态。
计算属性和动作只能通过这个来访问。更改状态也是如此。
从一个实例成员的 actions
中发送的动作只会修改指定存储的状态。
另一方面,从一个静态成员的 actions
中发送的动作将修改所有具有公共 State 泛型类型的存储中管理的所有状态。
在 ViewController 中实现的 Store 如下所示
final class CounterViewController: UIViewController {
@IBOutlet private weak var counterLabel: UILabel!
private let store = Store<CounterState>(state: .init(), mutations: .init(), executor: .queue(.global()))
override func viewDidLoad() {
super.viewDidLoad()
store.computed.countTextValues.bind(to: counterLabel, \.text)
}
@IBAction func incrementButtonTapped(sender: UIButton) {
store.actions.increment() // Store<CounterState>.actions.increment()
}
@IBAction func decrementButtonTapped(sender: UIButton) {
store.actions.decrement() // Store<CounterState>.actions.decrement()
}
}
关于 VueFluxReactive
VueFluxReactive 是一个用于观察状态变化的微级响应式系统。
它设计用来替换现有的功能强大的响应式框架,如 RxSwift 和 ReactiveSwift,尽管这些框架的学习和引入成本很高。
当然,VueFlux 可以与这些框架一起使用,因为 VueFluxReactive 是独立的。
VueFluxReactive 由以下原语组成。
汇点
这种类型有生成信号的方式。
可以向汇点发送值,通过观察生成的信号来接收它。
汇点生成的信号不保留最新值。
实际上,它用于从 State 向 ViewController 发送命令(例如显示另一个 ViewController)。
不能递归传递值.
let sink = Sink<Int>()
let signal = sink.signal
signal.observe { print($0) }
sink.send(value: 100)
// prints "100"
信号
一个推驱动的数据流,随时间发送值变化。
值将同时发送到所有已注册的观察者。
所有值的更改都通过这个原语进行。
let sink = Sink<Int>()
let signal = sink.signal
signal.observe { print("1: \($0)") }
signal.observe { print($2: \($0)") }
sink.send(value: 100)
sink.send(value: 200)
// prints "1: 100"
// prints "2: 100"
// prints "1: 200"
// prints "2: 200"
变量
变量代表一个线程安全的可变值,允许通过从它生成的信号来观察它的变化。
当开始观察时,信号转发最新值。所有值的更改都在之后交付。
不能递归传递值.
let variable = Variable(0)
variable.signal.observe { print($0) }
variable.value = 1
print(variable.value)
variable.signal.observe { print($0) }
/// prints "0"
/// prints "1"
/// prints "1"
/// prints "1"
常量
它是一种使变量只读的包装器。
从变量生成的常量反映了其变量变化的更改。
就像变量一样,最新的值和值更改通过信号转发。但常量不允许直接更改。
let variable = Variable(0)
let constant = variable.constant
constant.signal.observe { print($0) }
variable.value = 1
print(constant.value)
constant.signal.observe { print($0) }
/// prints "0"
/// prints "1"
/// prints "1"
/// prints "1"
高级用法
执行者
执行者决定了函数的执行上下文,如主线程、全局队列等。
一些上下文是默认构建的。
-
立即
立即同步执行函数。 -
主线程
如果执行线程是主线程,则立即同步执行。否则,将其排队到主队列。 -
队列(_ dispatchQueue: DispatchQueue)
所有函数都将排队到给定的调度队列。
在以下情况下,存储将操作提交到全局队列上的变异。
let store = Store<CounterState>(state: .init(), mutations: .init(), executor: .queue(.global()))
如果您观察到以下情况,观察器函数将在全局后台队列中执行。
store.computed.valueSignal
.observe(on: .queue(.global(qos: .background)))
.observe { value in
// Executed on global background queue
}
信号运算符
VueFluxReactive限制功能方法AMAP。
然而,包括了一些基本运算符以方便使用。
这些运算符将信号转换为新支持的信号,这意味着信号的不变性得到保持。
map
map运算符用于转换信号中的值。
let sink = Sink<Int>()
let signal = sink.signal
signal
.map { "Value is \($0)" }
.observe { print($0) }
sink.send(value: 100)
sink.send(value: 200)
// prints "Value is 100"
// prints "Value is 200"
observe(on_MetaData:)
将所有值都在指定的执行者上下文中转发。
let sink = Sink<Int>()
let signal = sink.signal
signal
.observe(on: .mainThread)
.observe { print("Value: \($0), isMainThread: \(Thread.isMainThread)") }
DispatchQueue.global().async {
sink.send(value: 100)
sink.send(value: 200)
}
// prints "Value: 100, isMainThread: true"
// prints "Value: 200, isMainThread: true"
可销毁的
可销毁的表示可以销毁的东西,通常是在Signal上注销已注册的观察者。
let disposable = signal.observe { value in
// Not executed after disposed.
}
disposable.dispose()
一次性作用域
一次性作用域作为一次性资源的管理器。
这将在初始化或销毁时终止所有附加的可处置对象。
例如,当具有 DisposableScope 属性的 ViewController 被销毁时,所有可处置对象都会被终止。
var disposableScope: DisposableScope? = DisposableScope()
disposableScope += signal.observe { value in
// Not executed after disposableScope had deinitialized.
}
disposableScope = nil // Be disposed
作用域内观察
在观察中,你可以将 AnyObject
作为 duringScopeOf:
的参数传递。
当对象被初始化时,正在观察信号观察者函数将被销毁。
signal.observe(duringScopeOf: self) { value in
// Not executed after `self` had deinitialized.
}
绑定
绑定使得目标对象的值更新为通过信号接收到的最新值。
目标对象被初始化后,绑定不再有效。
默认情况下,绑定在 主线程 上工作。
闭包绑定。
text.signal.bind(to: label) { label, text in
label.text = text
}
智能键路径绑定。
text.signal.bind(to: label, \.text)
绑定器
extension UIView {
func setHiddenBinder(duration: TimeInterval) -> Binder<Bool> {
return Binder(target: self) { view, isHidden in
UIView.transition(
with: view,
duration: duration,
options: .transitionCrossDissolve,
animations: { view.isHidden = isHidden }
)
}
}
}
isViewHidden.signal.bind(to: view.setHiddenBinder(duration: 0.3))
共享存储
你应该创建 Store 的一个共享实例来管理应用程序中共享的状态。
虽然你可以将其定义为全局变量,但优雅的方法是重写 Store 并定义一个静态成员 shared
。
final class CounterStore: Store<CounterState> {
static let shared = CounterStore()
private init() {
super.init(state: .init(), mutations: .init(), executor: .queue(.global()))
}
}
全局分发
VueFlux 也可以作为一个全局事件总线。
如果你从一个充当 Store 静态成员的 actions
函数调用函数,那么所有具有相同通用类型为 State 的存储管理的状态都将受到影响。
let store = Store<CounterState>(state: .init(), mutations: .init(), executor: .immediate)
print(store.computed.count.value)
Store<CounterState>.actions.increment()
print(store.computed.count.value)
// prints "0"
// prints "1"
需求
- Swift4.1+
- OS X 10.9+
- iOS 9.0+
- watchOS 2.0+
- tvOS 9.0+
安装
CocoaPods
如果使用VueFlux与VueFluxReactive,请在Podfile中添加以下内容
use_frameworks!
target 'TargetName' do
pod 'VueFluxReactive'
end
或者如果与第三方响应式框架一起使用
use_frameworks!
target 'TargetName' do
pod 'VueFlux'
# And reactive framework you like
end
并运行
pod install
Carthage
将以下内容添加到Cartfile中
github "ra1028/VueFlux"
并运行
carthage update
示例项目
- VueFluxExample-GitHub:使用GitHub用户搜索API的简单示例。
贡献
欢迎Fork并提交pull request。
在提交pull request之前,请确保已通过包含的测试。
如果您的pull request包括新功能,请为它编写测试用例。
许可
VueFlux 和 VueFluxReactive 在 MIT 许可下发布。