ReactiveFeedback 0.10.0

ReactiveFeedback 0.10.0

测试已测试
Lang语言 SwiftSwift
许可证 MIT
发布最后发布2023年5月
SPM支持SPM

Anders HasergdortRohit Warrier维护。



  • Babylon iOS

ReactiveFeedback

单向响应式架构。这是ReactiveSwiftRxFeedback实现。

文档

动机

iOS应用程序的要求变得很大。我们的代码需要管理很多状态,例如服务器响应、缓存数据、UI状态、路由等。有些人可能会说响应式编程可以帮我们很多忙,但如果不正确使用,它可能会对你代码库造成更多的伤害。

这个库的目标是提供一个简单直观的方法来设计反应性状态机。

核心概念

状态

状态是唯一的事实来源。它表示系统的状态,通常是一个纯Swift类型(不包含任何ReactiveSwift原语)。状态是不可变的。从一种状态转换到另一种状态的唯一方式是发出一个事件

struct Results<T: JSONSerializable> {
    let page: Int
    let totalResults: Int
    let totalPages: Int
    let results: [T]

    static func empty() -> Results<T> {
        return Results<T>(page: 0, totalResults: 0, totalPages: 0, results: [])
    }
}

struct Context {
    var batch: Results<Movie>
    var movies: [Movie]

    static var empty: Context {
        return Context(batch: Results.empty(), movies: [])
    }
}

enum State {
    case initial
    case paging(context: Context)
    case loadedPage(context: Context)
    case refreshing(context: Context)
    case refreshed(context: Context)
    case error(error: NSError, context: Context)
    case retry(context: Context)
}
事件

表示在系统中可能发生,可能导致新状态转换的所有可能事件。

enum Event {
    case startLoadingNextPage
    case response(Results<Movie>)
    case failed(NSError)
    case retry
}
Reducer

Reducer是一个纯函数,其签名是(State, Event) -> State。虽然事件代表导致状态变化的结果,但它实际上不是什么造成这种变化的原因。事件只是这样一个代表,表示从一种状态转换到另一种状态的意图。真正引起状态变化的是对应事件的具体体现——Reducer。Reducer是唯一可以改变状态的地方。

static func reduce(state: State, event: Event) -> State {
    switch event {
    case .startLoadingNextPage:
        return .paging(context: state.context)
    case .response(let batch):
        var copy = state.context
        copy.batch = batch
        copy.movies += batch.results
        return .loadedPage(context: copy)
    case .failed(let error):
        return .error(error: error, context: state.context)
    case .retry:
        return .retry(context: state.context)
    }
}
意见反馈

虽然 State 代表了系统在特定时间的状态,Event 代表状态变化的触发器,Reducer 是根据当前状态和接收到的事件类型改变状态的纯函数,但目前还没有一种类型可以针对特定当前状态发出事件。这是 Feedback 的职责。它实际上是一个“处理引擎”,它会监听当前 State 的变化并发出相应的下一事件。它由一个具有签名 Signal<State, NoError> -> Signal<Event, NoError> 的纯函数表示。Feedback 不直接修改状态。相反,它们只发出事件,然后导致 reducers 中的状态发生变化。

public struct Feedback<State, Event> {
    public let events: (Scheduler, Signal<State, NoError>) -> Signal<Event, NoError>
}

func loadNextFeedback(for nearBottomSignal: Signal<Void, NoError>) -> Feedback<State, Event> {
    return Feedback(predicate: { !$0.paging }) { _ in
        return nearBottomSignal
            .map { Event.startLoadingNextPage }
        }
}

func pagingFeedback() -> Feedback<State, Event> {
    return Feedback<State, Event>(skippingRepeated: { $0.nextPage }) { (nextPage) -> SignalProducer<Event, NoError> in
        return URLSession.shared.fetchMovies(page: nextPage)
            .map(Event.response)
            .flatMapError { (error) -> SignalProducer<Event, NoError> in
                return SignalProducer(value: Event.failed(error))
            }
        }
}

func retryFeedback(for retrySignal: Signal<Void, NoError>) -> Feedback<State, Event> {
    return Feedback<State, Event>(skippingRepeated: { $0.lastError }) { _ -> Signal<Event, NoError> in
        return retrySignal.map { Event.retry }
    }
}

func retryPagingFeedback() -> Feedback<State, Event> {
    return Feedback<State, Event>(skippingRepeated: { $0.retryPage }) { (nextPage) -> SignalProducer<Event, NoError> in
        return URLSession.shared.fetchMovies(page: nextPage)
            .map(Event.response)
            .flatMapError { (error) -> SignalProducer<Event, NoError> in
                return SignalProducer(value: Event.failed(error))
            }
        }
}

流程

  1. 如您从上面的图中所示,我们总是从一个初始状态开始。
  2. 每次对 State 的更改都将传递到系统中添加的所有 Feedback 循环。
  3. Feedback 然后决定是否应该对一个 State 的子集(例如调用 API、监听 UI 事件)执行任何操作,通过派发一个 Event,或者通过返回 SignalProducer.empty 忽略它。
  4. 派发的 Event 然后前往 Reducer,它应用该事件并返回一个新状态的值。
  5. 然后循环从头开始(见 2)。
示例
let increment = Feedback<Int, Event> { _ in
    return self.plusButton.reactive
        .controlEvents(.touchUpInside)
        .map { _ in Event.increment }
}

let decrement = Feedback<Int, Event> { _ in
    return self.minusButton.reactive
        .controlEvents(.touchUpInside)
        .map { _ in Event.decrement }
}

let system = SignalProducer<Int, NoError>.system(initial: 0,
    reduce: { (count, event) -> Int in
        switch event {
        case .increment:
            return count + 1
        case .decrement:
            return count - 1
        }
    },
    feedbacks: [increment, decrement])

label.reactive.text <~ system.map(String.init)

优势

待办