Loop 2.0.0

Loop 2.0.0

Anders Ha维护。



Loop 2.0.0

  • ReactiveCocoa社区

单向响应式架构。这是ReactiveSwiftRxFeedback对应物。

文档

动机

iOS应用的需求已经变得巨大。我们的代码必须管理很多状态,例如服务器响应、缓存数据、UI状态、路由等。有人说响应式编程可以帮助我们很多,但在错误的手中,它可能对您的代码库造成更大的损害。

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

核心概念

状态

State 是事实的唯一来源。它代表您系统的一个状态,通常是一个普通的Swift类型(不包含任何ReactiveSwift基元)。您的状态是不可变的。从一State 到另一State 的唯一方式是发出一个 Event

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的纯函数。虽然Event代表导致状态变化的动作,但它实际上并不是状态变化的原因。一个Event只是代表从一种状态转换到另一种状态的意图。真正导致状态变化,对应事件的具象是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)
    }
}
反馈

虽然状态表示系统在给定时间的位置,Event代表状态变化的触发器,而Reducer是一种基于当前状态和接收事件类型的纯函数,改变状态,但还没有给出特定当前状态下的事件类型。这就是反馈的职责。它本质上是一个“处理引擎”,监听当前状态的变化,并发射相应的后续事件。它由一个签名为Signal<State, NoError> -> Signal<Event, NoError>的纯函数表示。反馈不直接更改状态。相反,它们只发射事件,然后在Reducer中导致状态变化。

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. 每个对状态的改变都将传递给系统中添加的所有反馈循环。
  3. 反馈随后决定是否应该对状态的子集执行操作(例如调用API,观察UI事件)通过派发一个事件,或者忽略它通过返回SignalProducer.empty
  4. 触发 事件 后,进入 Reducer 并应用该事件,然后返回一个新的 State 值。
  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)

优势

待办

致谢

这是一个来自 Babylon Health 的 ReactiveFeedback 项目的社区分支(带有 MIT 许可证)。