Swivel 0.1.3

Swivel 0.1.3

Akkyie 维护。



Swivel 0.1.3

  • Akio Yasui

Swivel

Build Status Coverage Status

又一架构框架。

灵感来源于 ElmRedux。针对 Swift 优化。

特性

  • Swifty 接口:类型安全和全面
  • 使用 Swift 4 KeyPaths 赋予出色的效率和价值观察
  • 使用 Effects 实现异步性和副作用的结构分离
  • 支持多线程访问(尚未完全测试)
  • 无特定响应式框架依赖(即将推出 RxSwift/ReactiveSwift 的响应式适配器)

安装

⚠️Swivel 仍在非常早期的开发阶段。请谨慎使用。

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来制作这些副作用

订阅

你可以使用Storesubscribe方法来监视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: falseskipRepeats: 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...)构建一个新的更新函数,该函数通过消息以串行方式更新指定的子状态。每个状态的返回效果将在所有状态改变完成之后调用。