ReactiveFeedback
单向响应式架构。这是ReactiveSwift的RxFeedback实现。
文档
动机
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))
}
}
}
流程
- 如您从上面的图中所示,我们总是从一个初始状态开始。
- 每次对
State
的更改都将传递到系统中添加的所有Feedback
循环。 Feedback
然后决定是否应该对一个State
的子集(例如调用 API、监听 UI 事件)执行任何操作,通过派发一个Event
,或者通过返回SignalProducer.empty
忽略它。- 派发的
Event
然后前往Reducer
,它应用该事件并返回一个新状态的值。 - 然后循环从头开始(见 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)
优势
待办