Keemun 2.0.0-beta02

Keemun 2.0.0-beta02

Pavel Annin 维护。



Keemun 2.0.0-beta02

  • Pavel Annin

License

概览

什么是Elm架构

Elm架构(Elm架构)是Elm编程语言中常用的一个流行软件架构模式。它根据面向函数编程和单向数据流提供开发结构。

Elm架构由三个主要组件组成

  • Model 这是以不可变方式存储的包含所有所需数据的应用程序状态。模型通过消息(Msg)进行更新。
  • Update 这是一个函数,它接收当前模型和消息并返回一个新的模型。它负责处理消息并更新应用程序状态。
  • View 这是一个函数,它接收当前模型并返回用户界面。视图显示模型中的数据,并通过生成新消息来响应用户操作。

Elm架构提供了应用程序逻辑与其呈现之间的清晰分隔,使代码更易于理解、模块化且易于测试。它还确保错误容错性,并防止许多与可变状态和副作用相关的问题。

什么是 Keemun?

Keemun 是一个跨平台的 Kotlin 框架,它提供了一个使用 Elm 架构模式编写共享代码的方法。

安装

CocoaPods

CocoaPods 是 Cocoa 项目的依赖管理器。您可以使用以下命令来安装它

$ gem install cocoapods

要使用 CocoaPods 将 Keemun 集成到您的 Xcode 项目中,请在您的 Podfile 中指定它

pod 'Keemun', '1.0.0'

然后,运行以下命令

$ pod install

Swift Package Manager

Swift Package Manager 是一个自动化 Swift 代码分布的工具,它集成到 swift 编译器中。

一旦您已设置好 Swift 包,将 Keemun 添加为依赖项就像将其添加到您的 Package.swiftdependencies 值一样简单。

dependencies: [
    .package(url: "https://github.com/pavelannin/Keemun-Swift.git", from: "1.0.0")
]

组件

状态

状态 类描述在某个特定时刻,您的应用程序/特性/屏幕/视图等的状态。

启动

启动 是初始化的地方。它的方法返回初始化的 状态 及初始的副作用集合。

消息

任何 消息 都表示改变状态的意图。消息 由用户界面/业务逻辑发送。

更新

更新 是放置逻辑的地方。它的方法将 状态消息 作为参数,并返回新的 状态 和一系列 副作用。其想法是在一个纯函数中修改 状态,并根据收到的 消息 以及当前 状态 发送一个副作用。

Effect

任何 Effect 都代表调用您业务逻辑部分的一个意图。根据接收到的消息,Effect 由更新发送。

EffectHandler

EffectHandler 是执行业务逻辑的位置。它的方法接受 effectdispatch 作为参数。想法是作为一个函数,根据接收到的效果向 dispatch 发送消息并执行操作。EffectHandler 是一个挂起函数,每个效果都在单独的协程中执行。

Connector

Connector 是一个实体,它包含 Store 的实例。

StoreParams

StoreParams 是一个容器,它在一个地方持有 StartUpdateEffectHandler 以创建一个 Store。它为创建它提供了多个方便的重载函数,带有可选参数。

FeatureParams

FeatureParams 是一个容器,包含 StoreParams、将 State 转换为 ViewState 的函数以及其他创建 Connector 所需的参数。 FeatureParams 提供了几个便捷的重载函数,可以使用可选参数创建它。

示例

创建 StoreParams

struct CounterStoreParams: StoreParams, MsgSplitable {
    typealias Msg = SplitMsg<ExternalMsg, InternalMsg>

    func start() -> Start<Self> {
        .next(
            .init(
                syncCount: 0,
                asyncCount: 0,
                isAsyncRunning: false
            )
        )
    }

    static func externalUpdate(for msg: ExternalMsg, state: State) -> Update<Self> {
        switch msg {
        case .incrementSync:
            return .next(state) { $0.syncCount = $0.syncCount + 1 }
        case .decrementSync:
            return .next(state) { $0.syncCount = $0.syncCount - 1 }
        case .incrementAsync:
            return .next(state, effect: .increment(state.asyncCount)) { $0.isAsyncRunning = true }
        case .decrementAsync:
            return .next(state, effect: .decrement(state.asyncCount)) { $0.isAsyncRunning = true }
        }
    }

    static func internalUpdate(for msg: InternalMsg, state: State) -> Update<Self> {
        switch msg {
        case .completedAsyncOperation(let newValue):
            return .next(state) {
                $0.asyncCount = newValue
                $0.isAsyncRunning = false
            }
        }
    }

    func effectHandler(for effect: Effect, dispatch: @escaping InternalDispatch) async {
        switch effect {
        case .increment(let value):
            try! await Task.sleep(for: .seconds(1))
            dispatch(.completedAsyncOperation(value + 1))
        case .decrement(let value):
            try! await Task.sleep(for: .seconds(1))
            dispatch(.completedAsyncOperation(value - 1))
        }
    }

    struct State {
        var syncCount: Int
        var asyncCount: Int
        var isAsyncRunning: Bool
    }

    enum ExternalMsg {
        case incrementSync
        case decrementSync
        case incrementAsync
        case decrementAsync
    }

    enum InternalMsg{
        case completedAsyncOperation(Int)
    }

    enum Effect {
        case increment(Int)
        case decrement(Int)
    }
}

创建 FeatureParams

struct CounterFeatureParams: FeatureParams {
    typealias SParams = CounterStoreParams

    func stateTransform(_ state: CounterStoreParams.State) -> ViewState {
        ViewState(
            syncCount: String(state.syncCount),
            asyncCount: String(state.asyncCount),
            isAsyncRunning: state.isAsyncRunning
        )
    }

    struct ViewState {
        let syncCount: String
        let asyncCount: String
        let isAsyncRunning: Bool
    }
}

在 UI 层的使用

struct CounterFeatureView: View {
    @ObservedObject private var connector: KeemunConnector<CounterFeatureParams>

    init(_ connector: KeemunConnector<CounterFeatureParams>) {
        self.connector = connector
    }

    var body: some View {
        MainView(
            state: connector.state,
            syncIncrementAction: { connector.dispatch(.incrementSync) },
            syncDecrementAction: { connector.dispatch(.decrementSync) },
            asyncIncrementAction: { connector.dispatch(.incrementAsync) },
            asyncDecrementAction: { connector.dispatch(.decrementAsync) }
        )
    }
}

private struct MainView: View {...}

创建连接器实例

let connector = CounterFeatureParams().makeConnector(CounterStoreParams())
CounterFeatureView(connector)

示例项目

示例项目是一个带有两个计数器的屏幕:同步和异步。同步计数器在 Update 中修改,演示状态变化,而异步计数器在 EffectHandler 中修改,模拟异步业务逻辑。

屏幕截图