Mini-Swift 3.0.4

Mini-Swift 3.0.4

Jorge Revuelta HerreroSebastián Varela 维护。



 
依赖
RxSwift~> 5
SwiftNIOConcurrencyHelpers~> 2.10.0
 

Mini-Swift

Swift 中 Flux 架构的极简表达。

Mini 设计初衷就是要成为 Swift 应用程序中的一个一流的公民:macOS、iOS 和 tvOS 应用程序。使用 Mini,您可以为线程安全的应用程序创建可预测的单向数据流,专注于真正重要的内容:构建出色的应用程序。

Release Version Release Date Pod Platform GitHub

Build Status codecov Documentation

要求

  • Xcode 10 或更高版本
  • Swift 5.0 或更高版本
  • iOS 11 或更高版本
  • macOS 10.13 或更高版本
  • tvOS 11 或更高版本

安装

Swift Package Manager

  • 创建一个 Package.swift 文件。
// swift-tools-version:5.0

import PackageDescription

let package = Package(
  name: "MiniSwiftProject",
  dependencies: [
    .package(url: "https://github.com/bq/mini-swift.git"),
  ],
  targets: [
    .target(name: "MiniSwiftProject", dependencies: ["Mini" /*, "MiniPromises, MiniTasks"*/])
  ]
)
  • Mini 包含一个裸实现和两个用于简化库使用的辅助包:MiniTasksMiniPromises,这两个包都依赖于 Mini 基础或核心包。
$ swift build

Cocoapods

  • 将以下内容添加到您的 Podfile
pod "Mini-Swift"
# pod "Mini-Swift/MiniPromises"
# pod "Mini-Swift/MiniTasks"
  • 我们还提供了两个子规范用于日志记录和测试
pod "Mini-Swift/Log"
pod "Mini-Swift/Test"

使用方法

  • MiniSwift 是一个旨在简化 Swift 应用程序 Flux 驱动架构使用的库。由于其基于 Flux 的特性,它非常依赖其一些概念,如 StoreStateDispatcherActionTaskReducer

Architecture

状态

  • 架构的最小单元基于 状态 的概念。正如其名所示,状态 是指在某一时刻表示应用程序一部分的表示。

  • 状态是一个简单的 struct,它由不同的 Promises 构成,这些 Promises 保持表示当前状态的各个信息片段,可以如下实现。

  • 例如

// If you're using MiniPromises
struct MyCoolState: StateType {
    let cool: Promise<Bool>
}

// If you're using MiniTasks
struct MyCoolState: StateType {
    let cool: Bool?
    let coolTask: AnyTask
}
  • Promise 的默认内部状态为 idle。另一方面,Task 的默认内部状态也为 idle。这意味着没有任何 Action(见下文)开始对该 PromiseTask 进行任何操作。

  • PromiseTask 可以持有开发者在其实现中可能会遇到的任何额外属性,例如,绑持一个 Date 用于缓存使用

let promise: Promise<Bool> = .idle()
promise.date = Date()
// Later on...
let date: Date = promise.date

let task: AnyTask = .idle()
task.date = Date()
// Later on...
let date: Date = task.date
  • 状态 的核心思想是其 不可变性。因此,一旦创建,任何第三方对象都无法在架构流程控制之外对其作出修改。

  • 如示例所示,一个 状态 拥有一对 Task + Result(通常为任何对象),这与其执行相关。

  • 此外,Promise 对象统一了 状态 + 结果 对,因此它可以存储正在进行的任务的状态以及由它产生的关联负载。

操作

  • “操作”是通过对架构进行信息传递的信息块。任何可以符合“操作”协议的“struct”都可以得到应用,唯一的要求是其名字在应用程序中唯一。
struct RequestContactsAccess: Action {
  // As simple as this is.
}
  • “操作”不附带某些信息块,因此“Mini”为用户提供两个主要的实用协议:“CompletableAction”,“EmptyAction”和“KeyedPayloadAction”。

    • “CompletableAction”是“操作”协议的一种特殊化,使用户能够附加一个“Task”(任务)以及当“Task”成功时获得满足的某种类型的对象。
    struct RequestContactsAccessResult: CompletableAction {
      let promise: Promise<Bool>
    
      typealias Payload = Bool
    }
    • “EmptyAction”是“CompletableAction”的一种特殊化,其中“Payload”(有效负载)是“Swift.Void”(一个空的值),这意味着它只关联了一个“Promise”。
    struct ActivateVoucherLoaded: EmptyAction {
      let promise: Promise<Void>
    }
    • “KeyedPayloadAction”向“CompletableAction”添加一个“Key”(是一个可以哈希的键),这是一个特殊情况,其中相同的“操作”会产生可以一起组合的结果,通常是在一个“字典”中(例如,为搜索联系人按其主要电话号码进行分组)。
    struct RequestContactLoadedAction: KeyedCompletableAction {
    
      typealias Payload = CNContact
      typealias Key = String
    
      let promise: [Key: Promise<Payload?>]
    }

我们利用了使用“struct”,因此所有初始化器都是自动合成的。

例子是用“Promise”实现的,但还有用“Task”实现的等价形式。

存储

  • “存储”是决策和副作用通过输入和输出“操作”进行的地方。“存储”是一个用于继承并将其与之关联的“State”(状态)的泛型类。

  • “存储”可以像任何其他*RxSwift*的“Observable”一样产生“State”变化,这可以观察到。这样,一个“View”(视图)或任何其他您选择的对象可以收到由某个“存储”产生的新的“State”。

  • “存储”通过reducerGroup: ReducerGroup属性减少了某些“操作”的流动。

  • “存储”被实现的方式有两个泛型要求,一个是State: StateType,另一个是StoreController: Disposable。“StoreController”通常是一个包含执行可能被存储拦截的“操作”的日志的类,即一组URL请求,执行数据库查询等。

  • 通过泛型专门化,可以为每一对(StateStoreController)的情况重新编写reducerGroup变量,而无需对Store进行子类化。

extension Store where State == TestState, StoreController == TestStoreController {

    var reducerGroup: ReducerGroup {
        return ReducerGroup(
            // Using Promises
            Reducer(of: OneTestAction.self, on: dispatcher) { action in
                self.state = self.state.copy(testPromise: *.value(action.counter))
            },
            // Using Tasks
            Reducer(of: OneTestAction.self, on: dispatcher) { action in
                self.state = self.state.copy(data: *action.payload, dataTask: *action.task)
            }
        )
    }
}
  • 在上面的代码片段中,我们有一个关于“存储”如何工作的完整示例。我们使用ReducerGroup来指示“存储”将如何拦截OneTestAction类型的“操作”,并且每次拦截时,都会复制(不是黑魔法🧙‍,是一系列随着这个包一起分发的Sourcery脚本)存储的State

如果您正在使用SPM或Carthage,它们实际上并不允许随库一起分发资源。在这种情况下,我们建议您只在自己的项目中安装Sourcery,并使用可以直接从Templates目录下下载的模板。

  • 当与Store实例协同工作时,您可能保留了其reducerGroup的强引用,这可以通过subscribe()方法完成,该方法是一个可以像下面这样使用的Disposable
let bag = DisposeBag()
let store = Store<TestState, TestStoreController>(TestState(), dispatcher: dispatcher, storeController: TestStoreController())
store
    .subscribe()
    .disposed(by: bag)

分发器

  • 架构的最后一部分是Dispatcher。在应用程序范围内,应该只有一个活动状态的分发器,所有操作都从这里发出。
let action = TestAction()
dispatcher.dispatch(action, mode: .sync)
  • 一行代码就能通知定义了该类型Action的每个Store

高级用法

  • Mini构建在请求-响应单向流程之上。这由一对Action实现,一个用于请求在特定State中的更改,另一个用于根据操作结果更改State

  • 以下是一个代码示例,可以更简单地解释这一点。

使用Promise

// We define our state in first place:
struct TestState: StateType {
    // Our state is defined over the Promise of an Integer type.
    let counter: Promise<Int>

    init(counter: Promise<Int> = .idle()) {
        self.counter = counter
    }

    public func isEqual(to other: StateType) -> Bool {
        guard let state = other as? TestState else { return false }
        guard counter == state.counter else { return false }
        return true
    }
}

// We define our actions, one of them represents the request of a change, the other one the response of that change requested.

// This is the request
struct SetCounterAction: Action {
    let counter: Int
}

// This is the response
struct SetCounterActionLoaded: Action {
    let counter: Int
}

// As you can see, both seems to be the same, same parameters, initializer, etc. But next, we define our StoreController.

// The StoreController define the side-effects that an Action might trigger.
class TestStoreController: Disposable {
    
    let dispatcher: Dispatcher
    
    init(dispatcher: Dispatcher) {
        self.dispatcher = dispatcher
    }
    
    // This function dispatches (always in a async mode) the result of the operation, just giving out the number to the dispatcher.
    func counter(_ number: Int) {
        self.dispatcher.dispatch(SetCounterActionLoaded(counter: number), mode: .async)
    }
    
    public func dispose() {
        // NO-OP
    }
}

// Last, but not least, the Store definition with the Reducers
extension Store where State == TestState, StoreController == TestStoreController {

    var reducerGroup: ReducerGroup {
        ReducerGroup(
            // We can use Promises:
            // We set the state with a Promise as .pending, someone has to fill the requirement later on. This represents the Request.
            Reducer(of: SetCounterAction.self, on: self.dispatcher) { action in
                guard !self.state.counter.isOnProgress else { return }
                self.state = TestState(counter: .pending())
                self.storeController.counter(action.counter)
            },
            // Next we receive the Action dispatched by the StoreController with a result, we must fulfill our Promise and notify the store for the State change. This represents the Response.
            Reducer(of: SetCounterActionLoaded.self, on: self.dispatcher) { action in
                self.state.counter
                    .fulfill(action.counter)
                    .notify(to: self)
            }
        )
    }
}

使用任务

// We define our state in first place:
struct TestState: StateType {
    // Our state is defined over the Promise of an Integer type.
    let counter: Int?
    let counterTask: AnyTask

    init(counter: Int = nil,
         counterTask: AnyTask = .idle()) {
        self.counter = counter
        self.counterTask = counterTask
    }

    public func isEqual(to other: StateType) -> Bool {
        guard let state = other as? TestState else { return false }
        guard counter == state.counter else { return false }
        guard counterTask == state.counterTask else { return false }
        return true
    }
}

// We define our actions, one of them represents the request of a change, the other one the response of that change requested.

// This is the request
struct SetCounterAction: Action {
    let counter: Int
}

// This is the response
struct SetCounterActionLoaded: Action {
    let counter: Int
    let counterTask: AnyTask
}

// As you can see, both seems to be the same, same parameters, initializer, etc. But next, we define our StoreController.

// The StoreController define the side-effects that an Action might trigger.
class TestStoreController: Disposable {
    
    let dispatcher: Dispatcher
    
    init(dispatcher: Dispatcher) {
        self.dispatcher = dispatcher
    }
    
    // This function dispatches (always in a async mode) the result of the operation, just giving out the number to the dispatcher.
    func counter(_ number: Int) {
        self.dispatcher.dispatch(
            SetCounterActionLoaded(counter: number, 
            counterTask: .success()
            ),
            mode: .async)
    }
    
    public func dispose() {
        // NO-OP
    }
}

// Last, but not least, the Store definition with the Reducers
extension Store where State == TestState, StoreController == TestStoreController {

    var reducerGroup: ReducerGroup {
        ReducerGroup(
            // We can use Tasks:
            // We set the state with a Task as .running, someone has to fill the requirement later on. This represents the Request.
            Reducer(of: SetCounterAction.self, on: dispatcher) { action in
                guard !self.state.counterTask.isRunning else { return }
                self.state = TestState(counterTask: .running())
                self.storeController.counter(action.counter)
            },
            // Next we receive the Action dispatched by the StoreController with a result, we must fulfill our Task and update the data associated with the execution of it on the State. This represents the Response.
            Reducer(of: SetCounterActionLoaded.self, on: dispatcher) { action in
                guard self.state.rawCounterTask.isRunning else { return }
                self.state = TestState(counter: action.counter, counterTask: action.counterTask)
            }
        )
    }
}

文档

所有可用的文档都可以在这里找到:这里

维护者

作者及合作者

许可证

Mini-Swift 在 Apache 2.0 许可下可用。有关更多信息,请参阅 LICENSE 文件。