Swivel
又一架构框架。
灵感来源于 Elm 和 Redux。针对 Swift 优化。
特性
- Swifty 接口:类型安全和全面
- 使用 Swift 4 KeyPaths 赋予出色的效率和价值观察
- 使用 Effects 实现异步性和副作用的结构分离
- 支持多线程访问(尚未完全测试)
- 无特定响应式框架依赖(即将推出 RxSwift/ReactiveSwift 的响应式适配器)
安装
Carthage
github "akkyie/Swivel" ~> 0.1
CocoaPods
pod "Swivel", "~> 0.1"
SwiftPM
.package(url: "https://github.com/akkyie/Swivel.git", from: "0.1"),
概念
状态描述了一个状态。想想你要实现一个计数器
struct CounterState {
var count: Int
}
计数器应该可以被增加,有时也可以被减少。你可以枚举状态是如何更新的,作为消息
enum CounterMessage: Message {
case increment(amount: Int)
case decrement(amount: Int)
}
现在你可以描述当CounterState
接收到消息时它是如何更新的,以使它符合State
协议。
struct CounterState: State {
var count: Int = 0
static func update(_ state: inout CounterState, with message: Message) -> Effect? {
guard let message = message as? CounterMessage else { return nil }
switch message {
case let .increment(amount):
state.count += amount
return nil
case let .decrement(amount):
state.count -= amount
return nil
}
}
}
Swivel中的状态可以被描述为Elm中的Model+Update,或Redux中的State+Reducer。
消息显然在Elm中是Msg
,或在Redux中是Action。
状态中的update
函数应该是纯的(类似于Elm的更新和Redux的reducers。)你不应该进行任何异步操作,例如API调用或网络数据库访问。使用从Elm的Command中借用的概念Effect来制作这些副作用。
订阅
你可以使用Store
的subscribe
方法来监视State
的变化。
默认情况下,闭包在订阅后会立即被调用,并且每次派发消息时都会被调用,即使状态没有改变
let store = Store(initialState: CounterState(count: 0))
let unsubscribe = store.subscribe(\CounterState.count) { count in
print("Count: \(count)")
}
store.dispatch(CounterMessage.increment(1))
store.dispatch(CounterMessage.increment(2))
store.dispatch(CounterMessage.increment(0))
store.dispatch(CounterMessage.increment(0))
// Count: 0
// Count: 1
// Count: 3
// Count: 3
// Count: 3
你可以通过传递immediately: false
或skipRepeats: true
来改变这种行为
let store = Store(initialState: CounterState(count: 0))
let unsubscribe = store.subscribe(\CounterState.count, immediately: false, skipRepeats: true) { count in
print("Count: \(count)")
}
store.dispatch(CounterMessage.increment(1))
store.dispatch(CounterMessage.increment(2))
store.dispatch(CounterMessage.increment(0))
store.dispatch(CounterMessage.increment(0))
// Count: 1
// Count: 3
你只能在订阅的值(如上一个例子中的\CounterState.count
)是Equatable
时,指定skipRepeats: true
。
异步和副作用
Effect是状态中的update
函数返回的,描述了异步派发或任何副作用的程序。示例
enum CounterEffect {
static func requestAndIncrement() -> Effect {
return { dispatch in
request("https://pastebin.com/raw/uQ8LH0Gr") { (result: Int) in
dispatch(CounterMessage.increment(amount: result))
}
}
}
}
struct CounterState: State {
var count: Int = 0
var isLoading: Bool = false
static func update(state: inout CounterState, message: Message) -> Effect? {
guard let message = message as? CounterMessage else { return nil }
switch message {
case let .increment(amount):
state.count += amount
state.isLoading = false
return nil
case let .requestAndIncrement():
state.isLoading = true
return CounterEffect.requestAndIncrement() // <-- Returning an Effect
}
}
}
关注点分离:子状态和更新组合
你的状态可能作为其他状态属性存在,以便将一个状态划分为若干个子状态。
struct AppState: State {
var user: UserState
var auth: AuthState
var document: DocumentState
static func update(_ state: inout AppState, with message: Message) -> Effect? {
let update = makeSerialUpdate(\.user, \.auth, \.document)
return update(state, message)
}
}
makeSerialUpdate(states: State...)
构建一个新的更新函数,该函数通过消息以串行方式更新指定的子状态。每个状态的返回效果将在所有状态改变完成之后调用。