MiniSwift 1.0.0

MiniSwift 1.0.0

Jorge Revuelta Herrero维护。



 
依赖
RxSwift~> 5
SwiftNIOConcurrencyHelpers~> 2.10.0
 

MiniSwift 1.0.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/minuscorp/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,由不同的 Promise 组成,这些 Promise 包含代表当前状态的单个信息块,这可以按照以下方式实现。

  • 例如

// 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
  • 《状态》的核心思想是其 不可变性,因此一旦创建,第三方对象就无法在不受架构流程控制的情况下对其进行修改。

  • 如示例所示,一个 State 有一个 Task + Result 的对 通常(可以是任何对象,如果有的话),这与 Task 的执行相关。在上述示例中,CoolTask 通过其 Reducer 负责使用 Task 结果完成 Action,并进一步提供新的 State

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

操作

  • “操作”是通过架构分发的信息片段。任何struct都可以符合“操作”协议,唯一的要求是它在每个应用程序中有一个唯一名称。
struct RequestContactsAccess: Action {
  // As simple as this is.
}
  • 由于“操作”不附加相关信息,因此Mini为用户提供两个主要的实用协议:CompletableActionEmptyActionKeyedPayloadAction

    • CompletableAction是“操作”协议的一种特殊化形式,允许用户附加Task和当Task成功时得到满足的某种类型的对象。
    struct RequestContactsAccessResult: CompletableAction {
      let promise: Promise<Bool>
    
      typealias Payload = Bool
    }
    • EmptyActionCompletableAction的一种特殊化,其中PayloadSwift.Void,这意味着它只关联一个Promise
    struct ActivateVoucherLoaded: EmptyAction {
      let promise: Promise<Void>
    }
    • KeyedPayloadActionCompletableAction添加一个Key(它是Hashable)。这是一个特殊情况,其中相同的“操作”可以生成可以分组在一起的结果,通常在Dictionary(即搜索联系人的操作,并按他们的主电话号码分组)下。
    struct RequestContactLoadedAction: KeyedCompletableAction {
    
      typealias Payload = CNContact
      typealias Key = String
    
      let promise: [Key: Promise<Payload?>]
    }

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

示例使用Promise完成,但使用Task也有等效功能。

存储

  • “存储”是决策和副作用通过传入和传出的“操作”进行的中心。一个“存储”是一个通用类,可用来继承并关联一个“状态”。

  • “存储”可以产生像任何其他RxSwiftObservable一样的“状态”变化,可以被观察到。这样,一个View或其他任何选择的对象都可以接收由某个“存储”产生的新的“状态”。

  • 通过var reducerGroup: ReducerGroup属性,存储减少了大量“操作”的流动。

  • “存储”的实现方式有两个泛型要求,一个State: StateType和一个StoreController: Disposable。通常,“存储控制器”是一个包含执行可能被存储拦截的“操作”逻辑的类的名字,例如,一组URL请求,执行数据库查询等。

  • 通过泛型特殊化,可以不需要继承“存储”即可根据每种“状态”和“存储控制器”对的组合重写reducerGroup变量。

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脚本来实现的)。

如果您正在使用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。在一个应用范围内,应该只有一个活动的Dispatcher,从那里的每个动作都会进行分发。
let action = TestAction()
dispatcher.dispatch(action, mode: .sync)
  • 一行代码就可以通知所有为此类型Action定义了reducer的Store

高级用法

  • Mini是基于请求-响应单向流程构建的。这是通过使用一对Action来实现的,一个用于请求改变某个State的请求,另一个Action用于根据操作结果突变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 文件。