Mobius 是一个用于管理状态进化和副作用的功能响应式框架。它强调关注点分离、可测试性和代码中状态部分的隔离。
Mobius.swift 是基于 Swift 和 Apple 生态系统的原生 Mobius Java 框架 的实现。欲了解更多信息,请参阅用户指南的 wiki。您还可以观看 来自 Android @Scale 介绍 Mobius 的演讲。
此仓库包含核心 Mobius 框架以及适用于常见开发场景和测试的附加组件。
兼容性
环境 | 详情 |
---|---|
10.0+ | |
11.0+ | |
Swift 5.0 |
安装
Mobius.swift 支持大多数流行的依赖管理器。请选择您首选的方法查看说明
Swift 包管理器
Mobius 可以使用 Swift 包管理器为所有 Apple 平台构建。
请在您的 Package.swift
中添加以下条目
.package(url: "https://github.com/spotify/Mobius.swift.git", .upToNextMajor(from: "0.3.0"))
CocoaPods
Mobius 只能使用 CocoaPods 构建用于 iOS 的 Mobius。对于其他平台,请使用 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没有一种方式在单个仓库中指定子规格。因此,Carthage将自动拉动我们在
MobiusNimble
中提供测试辅助的依赖。如果您不打算使用它,可以简单地选择不在项目中链接此库。
Mobius实战 - 构建计数器
Mobius的目标是让您更好地控制您的应用程序状态。您可以将状态视为应用程序中所有变量当前值的快照。在Mobius中,我们将所有状态封装在一个称为模型(Model)的数据结构中。
模型(Model)可以表示为任何您喜欢的类型。在此示例中,我们将构建一个简单的计数器,因此所有状态都可以包含在一个Int
typealias CounterModel = Int
Mobius不允许您直接操作状态。为了更改状态,您必须向框架发送消息,说明您想执行的操作。我们称这些消息为事件(Employee)。在我们的例子中,我们将想要增加和减少我们的计数器。让我们用一个enum
来定义这些情况
enum CounterEvent {
case increment
case decrement
}
现在我们有了模型(Model)和一些事件(Employee),我们需要为Mobius提供一组规则,以便它可以为我们更新状态。我们通过提供给框架一个函数来实现,该函数将按顺序调用来处理每个传入的事件(Employee)和最近的模型(Model),以生成下一个模型(Model)
func update(model: CounterModel, event: CounterEvent) -> CounterModel {
switch event {
case .increment: return model + 1
case .decrement: return model - 1
}
}
有了这些构建块,我们可以开始将我们的应用程序视为在事件响应下的离散状态之间的转换。但我们相信这个拼图中还缺少一个部分——即与状态之间的转换相关的副作用。例如,按下“刷新”按钮可能会将我们的应用程序置于“加载”状态,副作用是从我们的后端获取最新的数据。
在Mobius中,我们将这些副作用恰当地称为效果(Effect)。在我们的计数器示例中,让我们说,当用户尝试将计数器减少到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会将您在任何状态转换中返回的每个效果发送到一个称为效果处理器(Effect Handler)的地方。现在让我们做一个
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
行为准则
本项目遵循 开放行为准则。通过参与,你应遵守此准则。