Mobius 是一个用于管理状态演变和副作用的功能性响应式框架。它强调关注点分离、可测试性和隔离代码中的状态部分。
Mobius.swift 是针对 Swift 和 Apple 生态系统的原始 Mobius Java 框架的实现。要了解更多信息,请参阅 wiki 中的用户指南。您还可以观看一个关于 Android @Scale 介绍 Mobius 的演讲。
此仓库包含核心的 Mobius 框架,以及用于常见开发场景和测试的附加组件。
兼容性
环境 | 详情 |
---|---|
10.0+ | |
11.0+ | |
Swift 5.0 |
安装
Mobius.swift 支持大多数流行依赖管理器。选择您首选的方法以查看说明
Swift 包管理器
您可以使用 Swift 包管理器为所有 Apple 平台构建 Mobius。
将以下条目添加到您的 Package.swift
.package(url: "https://github.com/spotify/Mobius.swift.git", .upToNextMajor(from: "0.3.0"))
CocoaPods
Mobius 只能使用 CocoaPods 为 iOS 构建。对于其他平台,请使用 Swift 包管理器。
将以下条目添加到您的 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 包管理器。
将以下条目添加到您的 Cartfile
github "spotify/Mobius.swift" "0.3.0"
有关说明,请参阅 Carthage 文档。
注意:目前 Carthage 没有一种方法在单个仓库中指定 subspec。因此,Carthage 将自动从
MobiusNimble
中拉取我们用于提供测试帮助的依赖项。如果您的项目中不打算使用此库,您可以选择不将其链接到您的项目。
Mobius in Action - 构建一个计数器
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。
- ????
- 创建 pull request
行为准则
本项目遵循开放行为准则。参与其中,请遵守此准则。