单向响应式架构。这是ReactiveSwift的RxFeedback对应物。
文档
动机
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))
}
}
}
流程
- 如上图所示,我们始终从一个初始状态开始。
- 每个对
状态
的改变都将传递给系统中添加的所有反馈
循环。 反馈
随后决定是否应该对状态的子集执行操作(例如调用API,观察UI事件)通过派发一个事件
,或者忽略它通过返回SignalProducer.empty
。- 触发
事件
后,进入Reducer
并应用该事件,然后返回一个新的State
值。 - 然后循环从头开始(见 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 许可证)。