VueFluxReactive 1.6.0

VueFluxReactive 1.6.0

ra1028 维护。



  • ra1028

VueFlux

Swift 的单向状态管理架构——灵感来自 VuexFlux


Swift4 Build Status CodeBeat
CocoaPods Carthage Platform Lincense


介绍

VueFlux 是 Swift 的 Architecture,用于以单向数据流管理状态,灵感来自 VuexFlux.

VueFlux 支持多 store,因此所有 ViewControllers 都有指定的 store,规则的规则确保状态只能以可预测的方式突变。

store 还可以接收全局分发的 action。
这使得 ViewControllers 之间的依赖得到释放。此外,应用程序中的共享状态也可以通过共享的 store 实例来支持。

尽管 VueFlux 使您的项目更具有生产力和代码更易于阅读,但它也带来了更多概念和模板的成本。
如果您的项目是小型的,您可能不需要 VueFlux。
但是,随着项目规模的扩大,VueFlux 将成为处理复杂数据流的最佳选择。

VueFlux 通过高效的响应式系统接收状态变化。 VueFluxReactive 是 µ 响应式框架,兼容此架构。
任意第三方响应式框架(例如 RxSwiftReactiveSwift,等)也可以与 VueFlux 一起使用。

VueFlux Architecture


关于 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

示例项目


贡献

欢迎Fork并提交pull request。

在提交pull request之前,请确保已通过包含的测试。
如果您的pull request包括新功能,请为它编写测试用例。


许可

VueFlux 和 VueFluxReactive 在 MIT 许可下发布。