Mobius 是一个功能性的响应式框架,用于管理状态演变和副作用。它强调关注点的分离、可测试性和隔离代码中有状态的部分。
Mobius.swift 是针对 Swift 和 Apple 生态系统重构的原始 Mobius Java 框架。要了解更多信息,请参阅wiki中的用户指南。您还可以观看一个在 Android @Scale 介绍 Mobius 的演讲。
此仓库包含核心 Mobius 框架以及用于常见开发场景和测试的插件。
兼容性
环境 | 详情 |
---|---|
10.0+ | |
11.0+ | |
Swift 5.0 |
安装
Mobius.swift 支持大多数流行的依赖管理器。选择您首选的方法查看说明
Swift Package Manager
Mobius 可以使用 Swift Package Manager 为所有 Apple 平台构建。
将以下条目添加到您的 Package.swift
.package(url: "https://github.com/spotify/Mobius.swift.git", .upToNextMajor(from: "0.3.0"))
CocoaPods
Mobius 只能使用 CocoaPods 为 iOS 构建应用程序。对于其他平台,请使用 Swift Package Manager。
在您的 Podfile
中添加以下条目
pod 'MobiusCore', '0.3.0'
可选地,您还可以选择集成 MobiusExtras
、MobiusNimble
或 MobiusTest
pod 'MobiusExtras', '0.3.0'
pod 'MobiusNimble', '0.3.0'
pod 'MobiusTest', '0.3.0'
Carthage
Mobius 只能使用 Carthage 为 iOS 构建应用程序。对于其他平台,请使用 Swift Package Manager。
在您的 Cartfile
中添加以下条目
github "spotify/Mobius.swift" "0.3.0"
以下是在 Carthage 文档中解释的额外步骤。
注意:在此刻 Carthage 没有在单个仓库中指定 subspec 的方法。因此,Carthage 会自动拉取用于在
MobiusNimble
中提供测试辅助器的依赖。如果您不打算使用它,可以简单地选择不在项目中链接此库。
Mobius 在行动 - 构建计数值器
Mobius 的目标是让您更好地控制应用程序状态。您可以将状态视为应用程序中所有变量当前值的快照。在 Mobius 中,我们将所有状态封装在一个我们称为 模型 的数据结构中。
模型 可以表示您喜欢的任何类型。在这个例子中,我们将构建一个简单的计数器,因此我们所有的状态都可以包含在一个 Int
中。
typealias CounterModel = Int
Mobius 不允许您直接操作状态。为了改变状态,您必须向框架发送消息,说明您想做什么。我们把这些消息称为 事件。在我们的情况下,我们想增加和减少计数器。让我们使用 enum
来定义这些情况。
enum CounterEvent {
case increment
case decrement
}
现在我们有了 模型 和一些 事件,我们需要给 Mobius 一套规则,它可以使用这些规则代替我们更新状态。我们通过给框架一个函数来完成,该函数将按顺序被每个传入的 事件 和最新的 模型 调用,以生成下一个 模型。
func update(model: CounterModel, event: CounterEvent) -> CounterModel {
switch event {
case .increment: return model + 1
case .decrement: return model - 1
}
}
有了这些构建块,我们开始将应用程序视为由事件触发的离散状态之间的转换。但我们相信拼图中仍然有一块缺失——那就是与状态转换相关的副作用。例如,按下“刷新”按钮可能会将应用程序放入“加载”状态,同时副作用是从我们的后端获取最新的数据。
在 Mobius 中,我们恰当地将这些副作用称为 效应。在我们的计数器的情况下,让我们假设当用户尝试将计数器减到 0 以下时,我们将播放一个音效。让我们创建一个 enum
来代表所有可能的效果(在这个情况下只有一个)。
enum CounterEffect {
case playSound
}
我们现在需要扩展我们的 update
函数,以便它还能返回一组与某些状态转换关联的效应。这看起来像:
func update(model: CounterModel, event: CounterEvent) -> Next<CounterModel, CounterEffect> {
switch event {
case .increment:
return .next(model + 1)
case .decrement:
if model == 0 {
return .dispatchEffects([.playSound])
} else {
return .next(model - 1)
}
}
}
Mobius 将您在任何状态转换中返回的每个效应发送到一个我们称为 效应处理程序 的地方。现在让我们做一个吧。
import AVFoundation
private func beep() {
AudioServicesPlayAlertSound(SystemSoundID(1322))
}
let effectHandler = EffectRouter<CounterEffect, CounterEvent>()
.routeCase(CounterEffect.playSound).to { beep() }
.asConnectable
现在所有部件都已经就位,让我们把它们全部连接起来。
let application = Mobius.loop(update: update, effectHandler: effectHandler)
.start(from: 0)
让我们开始使用我们的计数器。
application.dispatchEvent(.increment) // Model is now 1
application.dispatchEvent(.decrement) // Model is now 0
application.dispatchEvent(.decrement) // Sound effect plays! Model is still 0
这涵盖了 Mobius 的基础知识。要了解更多信息,请访问我们的 wiki。
状态
Mobius.swift 正在接近 1.0 版本发布。我们已经在部署的功能中内部使用这个框架,但最近做了一些破坏性更改。版本 0.3.0 与之前的 0.2.0 版本不兼容,并包含一些较小更改的过时向后兼容包装器。这些过时版本将被移除,并进行一些其他添加更改,以形成 Mobius 1.0。
开发
- 克隆
- 启动项目
./Tools/bootstrap.sh
- 使用Xcode打开Mobius.xcodeproj。
- ????
- 创建PR
行为准则
本项目遵守开放行为准则。通过参与,你应遵守本准则。