MVVMCombine
制作更好的应用程序。更少的代码。使用 MVVMCombine 畅游 SwiftUI!
使用模型-视图-视图模型-协调器 (MVVM-C) 设计模式构建干净、像素完美且声明式的 UI。MVVMCombine 是一个专门为 Apple 的最新框架 Combine(与 SwiftUI 一起)开发的框架,它提供了逻辑流作为函数式响应式编程 (FRP) 的核心,具有易于阅读和自然编写的声明式 Swift 语法。
现在使用 MVVMCombine,视图模型负责以易于管理和管理的方式展示模型中的数据对象。在这方面,视图模型更多的是模型而不是视图,它处理了视图的大部分(如果不是全部)显示逻辑,并通过协调器处理导航行为。
目录
功能
- 使用属性包装器进行依赖注入
- 动态注册、解析或注入服务
- 为每个视图动态注册和注入视图模型
- 在其对应的视图模型中管理视图的生命周期
- 通过视图模型协调器协调视图的导航
- 根视图、链接、表单和标签项的协调器
- 动态可调用的视图模型输出工厂
- 动态成员查找视图模型输入
- 将自定义单元格视图绑定到视图项
- 完整文档
入门
需求
- iOS 13.0+ / macOS 10.15+ / tvOS 13.0+ / watchOS 6.0+
- Xcode 11.0+
- Swift 5+
安装
CocoaPods
CocoaPods 是 Cocoa 项目的依赖管理器。有关使用和安装说明,请访问他们的网站。要在 Xcode 项目中使用 CocoaPods 集成 MVVMCombine,请在您的 Podfile
中指定它
pod 'MVVMCombine'
工作原理
架构
以下图表说明了模型-视图-视图模型-协调器(MVVM-C)设计模式的基本架构
- 模型:指代领域模型或数据访问层,它们都表示内容。
- 视图:用户在屏幕上看到的结构、布局和外观。它显示模型表示,接收用户与视图的交互(点击、键盘、手势等),并通过定义链接视图和视图模型的数据绑定(属性、事件回调等)将这些交互传递给视图模型。
- 视图模型:视图的抽象,公开属性和命令。与MVC模式的控制器或MVP模式的展示者不同,MVVM有一个绑定器,它自动化视图与视图模型中绑定属性的通信。视图模型被描述为模型中数据的某种状态。
- 协调器:负责处理导航流程,根据从相应视图启动的视图模型事件决定何时何地移动。
有关MVVM-C iOS架构的详细信息,请参阅以下教程。
优点
以下是使用MVVM-C架构的一些优点:
- 不同类型代码的清晰分离应该会使得进入到那些更细粒度、更专注的部分并做出更改变得更容易,无需担心。
- 在MVVM中,每一段代码都更加细分,如果实现得当,您的外部和内部依赖将与核心逻辑的代码分开,在核心逻辑部分做出更改时进行测试。这使得针对核心逻辑编写单元测试变得容易很多。
- 这些部分更有可能变得更加可复用。
- 协调器简化了视图模型之间的导航逻辑和数据共享。
用法
依赖注入
通过子类化MwxServices
并重写registerServices()
来注册项目中使用的所有服务。
按如下方式注册每个服务
Mwx.register(BackendService.init).as(BackendProtocol.self).lifeCycle(.weakSingle)
其中lifeCycle
可以是single
、prototype
、weakSingle
或objectGraph
。
为了解决服务
let backendService = Mwx.resolve(BackendProtocol.self)
并且将其作为实例属性注入,使用@Service
如下所示
@Service var backendService: BackendProtocol
MVVM 流程
让所有视图遵守 MwxView
接口,并声明 vm
属性,以注入相应的视图模型,使用 @ViewModel
,如下所示
struct HomeView: MwxView
@ViewModel var vm: HomeViewModel
让视图模型遵守 MwxObservableViewModel
接口,并确定其视图为一个泛型,如下所示
class HomeViewModel: MwxObservableViewModel<HomeView>
如果使用导航视图,则让主体视图始终被 MwxNavigationBody
包装;如果使用标签视图,则使用 MwxTabBody
包装;否则只使用 MwxBody
,并将其绑定到其 vm
以同步视图及其视图模型生命周期 didAppear
和 didDisappear
,如下所示
var body: some View {
MwxNavigationBody {
Text("Hello World!")
}
.bind(to: vm)
}
一旦这样做,你现在可以覆盖视图模型生命周期,如下所示
override func didLoad() {
}
override func didAppear() {
}
override func didDisappear() {
}
列表单元格
让你的列表行遵守 MwxCell
接口,并声明 item
属性,以确定其相应的视图项,使用 @ViewItem
,如下所示
struct ListCell: MwxCell
@ViewItem var item: ListViewItem
让视图项遵守 MwxViewItem
接口,如下所示
class ListViewItem: MwxViewItem
协调器
为了创建一个新协调器,确定其视图模型和展示样式。目前支持4种展示样式:root
、tab
、link
或 sheet
。声明 MwxCoordinator
,如下所示
let detail = DetailViewModel.link(self)
对于标签协调器,在视图模型中覆盖 func tabs() -> [MwxTab]
,以确定要协调哪些标签,并生成相应的标签项,如下所示
let home = HomeViewModel.tab(self)
let contact = ContactViewModel.tab(self)
override func tabs() -> [MwxTab] {
return [
home,
contact
]
}
对于链接和表单协调器,确定由链接或表单协调的视图,如下所示
Text("Link here!").coordinated(by: vm.detail)
为了显示链接和表单协调器,在视图模型中使用 show()
,使用 pop()
来停用链接,并使用 dismiss()
来停用表单,如下所示
func showDetail() {
detail.show()
}
func save() {
pop()
}
为了声明一个回调,当协调器停用时使用 onDisappear: (() -> Void)
闭包,如下所示
let detail = DetailViewModel
.sheet(self)
.onDisappear {
print("Sheet Dismissed!")
}
输入
使用输出属性构建视图模型输入,MwxOutput
是一个动态可调用对象,因此您可以动态声明输入键,如下所示
let input = output(name: "Mike",
job: "Engineer")
在其协调器声明中传递输入到视图模型,如下所示
let detail = DetailViewModel
.sheet(self)
.with(input)
.onDisappear {
print("Sheet Dismissed!")
}
或者,您也可以在显示链接或表单协调器时传递它,如下所示
func showDetail() {
detail.show(with: input)
}
最后,在视图模型内部,使用动态成员查找来解析并获取给定键的任何输入值,如下所示
override func didAppear() {
if let name = input.name {
}
}
对于标签页协调器,它们的输入需要提供标题键和图像键,以渲染相应标签页项的标题和图像,如下所示
var contactInput: MwxInput {
output(title: "Contact",
image: "contact")
}
ContactViewModel
.tab(self)
.with(contactInput)
演示
Unicorn 是一个示例演示,总结了 MVVMCombine 的主要功能。基本上,你可以添加、编辑和删除独角兽。在演示中使用了标签页、链接和表格协调器。
在运行之前
在运行此演示之前,请访问 crudcrud,并复制在您的 REST 端点显示的特定 API 密钥,然后将其粘贴到 DataUrlService
中的 secret
属性。
这非常重要,以便后端 API 正常工作!
协调器层级
以下图例说明了在示例演示中管理视图导航的协调器层级
待办事项
- 运行时自动视图模型注册
- 运行时自动视图到视图模型的查找
- 支持自定义协调器
- 支持视图模型发布者输入
- 提供数组到列表/表单适配器
- 提供动态样式引擎
- 提供 iPad 分割视图的协调器
致谢
我要感谢
授权
MVVMCombine采用MIT授权发布。有关详情,请参阅LICENSE。
作者
Mohamed Salem