ReSwiftConsumer 0.9.0

ReSwiftConsumer 0.9.0

brownsoo 维护。



ReSwiftConsumer

现在是整理中。(现在开发中)

通过简单的示例项目(Example 项目)可以快速了解其工作原理。正在收集中 ... 您可以通过 Example 项目了解 ReSwift 状态如何通过该库来工作。

使用 ReSwiftConsumer,您只能消费感兴趣的 State 中属性的变化。并且您可以有独立的存储与全局状态分开。

CI Status Version License Platform codebeat badge

ReSwiftConsumer 通过连接属性选择函数(Property selector)和更改状态的消费函数(Changed property consumer, Observer)与 ReSwift,帮助开发人员对 State 的特定属性进行响应。通过使用 ReSwiftConsumer,您可以仅从 ReSwift 的 State 中检测到所需的属性更改。

使用属性选择函数实现多观察者,有一个用 extension 实现的例子,链接如下。 ReSwift+select.swift 我用 Promise 形式简洁地实现了我一直在思考的问题。Wow!!

StateConsumer

虽然 ReSwift 可以通过 newState 函数知道 State 发生了变化,但无法知道状态中哪些属性值发生了变化。如果 UI 组件与大量的 State 连接,则需要针对频繁的 State 更新进行复杂和细致的处理。正为此创建了 StateConsumer

StateConsumer 实现了以下 Consumer 协议,当状态变化时,会调用 consume 函数。

public protocol Consumer {
    associatedtype State
    func consume(old: State?, new: State?)
}

使用方法很简单。通过 add 函数,将关心的属性和对应消费函数连接起来即可。也可以观察到子 State 中的特定值。

struct AppState: StateType {
    var counter: Int = 0
    var name: String? = nil
    var works: [String] = []
}
let appStore = Store<AppState>(reducer: reducer, state: nil)

class CounterViewController: UIViewController, StoreSubscriber {
    typealias StoreSubscriberStateType = AppState
    
    @IBOutlet var counterLabel: UILabel!
    @IBOutlet var nameLabel: UILabel!
    var works: [String] = []
    let consumer = StateConsumer<AppState>()

    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
        appStore.subscribe(self)
        
        // add consumer of property selectively
        consumer.add({state in state.counter}, onCounterChanged)
        consumer.add({state in state.name}, onNameChanged)
        consumer.add({state in state.works}, onWorksChanged)
    }
    override func viewWillDisappear(_ animated: Bool) {
        appStore.unsubscribe(self)
        // remove all consumers
        consumer.removeAll()
        super.viewWillDisappear(animated)
    }

    func newState(state: AppState) {
        // pass new state into consumer
        consumer.consume(newState: state)
    }

    // MARK: Consumers

    private func onCounterChanged(old: Int, new: Int) {
        counterLabel.text = "\(new)"
    }

    private func onNameChanged(old: String?, new: String?) {
        nameLabel.text = new
    }

    private func onWorksChanged(old: [String]?, new: [String]) {
        self.works = new
    }
}

尚未对 StateConsumer 进行性能测试。不过,由于 StateConsumer内部包含状态实例,因此在处理大型 State 时,可能会出现内存或速度问题。因此,应该精心设计状态结构,并分片使用。为了解决此类问题,已创建了独立的存储。以下可查看详情。

分离存储(additional)

PageStoreSubscriber

已创建一个新的独立存储订阅者。

在 ReSwift 中,建议创建一个单一的存储进行使用。这一内容在 Redux 的指南中有很好的说明。但就应用开发而言,我需要一个面向屏幕的存储或非全局存储。当我们考虑通常的开发单位时,它是屏幕级的,并且我们设计了屏幕级的子状态结构。随着应用程序变得更加复杂,屏幕数量也会不断增加,一个状态结构体将变得更加复杂。而且执行中的屏幕控制器(ViewController)会被创建和销毁数十次,因此有很多临时数据。向全局状态添加属性值也有很多微不足道的属性。我对在全局状态中放入此类临时数据有些不自在。

如果全局状态与屏幕的生命周期相同,并且有相同的状态,那么我认为这将更容易进行开发。在创建屏幕控制器时,所需的初始状态值可以通过引用全局状态来创建,而当需要修改全局状态时,可以向全局存储发送动作。全局状态只需要包含真正的全局元素。

在 ReSwift 中,也提供了只选择性地订阅子状态的功能,并可以使用这种方式根据需要分离状态。但遗憾的是,在分离的子状态存储订阅者中,不能以订阅的形式访问全局状态的值。

例如,从选择性地订阅了 AppState 的 Sub State 实现 MainState 的 (UIViewController 或 UIView 等)上下文中,无法检测到 `authState` 或 `foregorund` 值的变化。而且,我想知道全局状态的变化。

struct AppState: StateType {
    var authState = AuthState()
    var foreground = false
    var mainState = MainState()
}

PageStoreSubscriber 通过使用分离的存储可以如下订阅 MainStateAppState

编写中...

首先,分离 AppState 和 MainState。

struct AppState: StateType {
    var authState = AuthState()
    var foreground = false
}
struct MainState: StateType {
    var count: Int = 10
}

MainVc 通过创建同时订阅 MainState 的形式创建了一个新的形态。在这里,《strong pageStore 是因为 MainVc 作为对象而存在,所以当 ViewController 被销毁时,同时销毁,但全局创建的 appStore 则在应用程序存在的情况下持续保持其状态。这样一来,我们可以像一个应用程序的范围一样使用状态,但实际上可以在每个 ViewController 中分开管理状态。

class MainVc: UIViewController, PageStoreSubscriber, StoreSubscriber {
    // for AppState
    typealias StoreSubscriberStateType = AppState
    let appConsumer = StateConsumer<AppState>()
    // for MainState
    typealias PageStoreSubscriberStateType = MainState
    typealias PageStoreInteractStateType = MainState
    private var pageStore: Store<MainState>?
    private let pageConsumer = StateConsumer<MainState>()
    private lazy var pageStoreSubscriber = RePageStoreSubscriber(subscriber: self)
    
    override func viewDidLoad() {
        super.viewDidLoad()
        pageStore = Store<MainState>(reducer: mainReducer,
                                    state: nil,
                                    middleware: [])

        // bind AppState
        appStore.subscribe(self)
        appConsumer.add({state in state?.foreground},  onForegroundChanged)
        // bind MainState
        pageStore?.subscribe(pageStoreSubscriber)
        pageConsumer?.add({state in state?.count}, onCountChanged)
    }

    deinit {
        // unbinding appStore, its consumer
        appConsumer.removeAll()
        appStore.unsubscribe(self)
        // unbinding pageStore, its consumer
        pageConsumer.removeAll()
        if pageStoreSubscriber != nil {
            pageStore?.unsubscribe(pageStoreSubscriber!)
        }
    }

    func newState(state: AppState) {
        appConsumer.consume(newState: state)
    }

    func newPageState(state: MainState) {
        pageConsumer.consume(newState: state)
    }

    // MARK: Consumers

    private func onForegroundChanged(curr: Bool) {
        print("AppState-  foreground \(curr)")
    }

    private func onCountChanged(prev: Int?, curr: Int) {
        print("MainState-  count: \(curr)"
    }
}

组件(additional)

该库包含了一些在 ViewController 单元中可以直接使用的组件。这些组件是以便于使用重复性操作的形式创建的。

  • RePageController : 已实现 PageStoreSubscriber 以直接使用,具有 ReSwift 提供的 StoreSubscriber 以及不同的 Store 和 Middleware。

  • StateViewController, StateNavigationController : 可以与 RePageController 连接的基本视图控制器。它们可以订阅或解除订阅与独立存储和 RePageController 采取的状态。

  • ConsumberBag: 用于选择性地收集 Consumer 并一次性删除时使用。

示例

要运行示例项目,请克隆仓库,然后从Example目录运行pod install命令。

查看示例应用程序的代码。

安装

CocoaPods

ReSwiftConsumer可通过CocoaPods获取。要安装,请将以下行添加到Podfile文件中。

pod 'ReSwift'
pod 'ReSwiftConsumer'

作者

brownsoo,@hansoo.labshansoo.labs

许可证

ReSwiftConsumer采用MIT许可证。更多信息请查看LICENSE文件。