Mobius 是一个用于管理状态演变和副作用的功能响应式框架。它强调关注点的分离、可测试性和隔离代码中的状态部分。
Mobius.swift 是针对 Swift 和 Apple 生态系统实现的原始 Mobius Java 框架。要了解更多信息,请参阅wiki中的用户指南。您还可以观看Android @Scale 介绍 Mobius 的演讲。
此仓库包含核心 Mobius 框架以及适用于常见开发场景和测试的附加组件。
兼容性
环境 | 详情 |
---|---|
10.0+ | |
11.0+ | |
Swift 5.0 |
安装
Mobius.swift 支持大多数流行的依赖管理器。选择您首选的方法以查看说明
Swift Package 管理器
Mobius 可以使用 Swift Package 管理器为所有 Apple 平台构建。
将以下条目添加到您的 Package.swift
.package(url: "https://github.com/spotify/Mobius.swift.git", .upToNextMajor(from: "0.3.0"))
CocoaPods
Mobius 仅支持使用 CocoaPods 构建 iOS。对于其他平台,请使用 Swift Package 管理器。
在您的 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 管理器。
在您的 Cartfile
中添加以下条目
github "spotify/Mobius.swift" "0.3.0"
有关说明,请参阅 Carthage 文档。
注意:目前Carthage还没有在单个仓库中指定子规格的方法。因此,Carthage会自动拉取用于在《MobiusNimble》中提供测试辅助工具的依赖。如果您不打算使用它,可以直接在项目中不链接这个库。
《Mobius实战——构建一个计数器》
Mobius的目的是让您更好地控制您的应用状态。您可以将状态想象为应用中所有当前变量值的快照。在Mobius中,我们将所有状态封装在一种被称为模型的数据结构中。
模型可以用你喜欢的任何类型表示。在这个示例中,我们将构建一个简单的计数器,所以所有状态都包含在一个Int
中。
typealias CounterModel = Int
Mobius不允许您直接操作状态。为了改变状态,您必须发送框架消息,说明您想做什么。我们称这些消息为事件。在我们的例子中,我们想要增加和减少计数器。让我们使用一个枚举
来定义这些情况
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 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的基础知识。想了解更多,请访问我们的维基。
状态
Mobius.swift 即将发布 1.0 版本。我们已在部署的功能中使用该框架,但最近进行了多项破坏性更改。0.3.0 版本与之前的 0.2.0 版本不兼容,并包含一些小更改的废弃向后兼容性包装。这些废弃的版本将被移除,并进行一些其他添加性更改,以形成 Mobius 1.0。
开发
- 克隆
- 启动项目
./Tools/bootstrap.sh
- 使用 Xcode 打开 Mobius.xcodeproj。
- ????
- 创建 PR
行为准则
本项目遵循 开放行为准则。通过参与,你应遵守此准则。