Verge.swift
📍一个适用于iOS的高效状态管理架构 - UIKit, SwiftUI📍
_ 获得单向数据流的更简单方式 _
_ 支持并发处理 _
支持这个项目
Verge:一个适用于SwiftUI和UIKit的高性能、可扩展的状态管理库
Verge是一个为Swift设计的高性能、可扩展的状态管理库,考虑到了真实世界的使用场景。它提供了一种轻量级且易于使用的方法来管理应用程序状态,无需复杂的操作和减速器。本指南将带您了解在Swift项目中使用Verge的基础知识。
核心概念和动机
Verge的设计考虑了以下概念:
- 受Flux库的启发,但注重将存储模式作为核心概念。
- 存储模式是Flux和Redux中的基本概念,专注于使用单一真实来源在组件之间共享状态。
- Verge不规定如何管理修改状态的操作。相反,它提供了一个简单的
commit
函数,该函数接受描述如何更改状态的闭包。 - 用户可以在Verge之上构建额外的层,例如实现基于枚举的操作以实现更结构化的状态管理。
- Verge支持多线程,确保快速、安全和高效的操作。
- 兼容UIKit和SwiftUI。
- 包含用于处理实际应用程序开发用例的API,如管理异步操作。
- 解决了在大型和复杂应用程序中更新状态的复杂性。
- 提供了一个ORM,用于高效管理大量实体。
- 为业务导向的应用程序设计。
开始使用
开始使用
要使用Verge,请按照以下步骤操作:
- 定义一个符合
Equatable
协议的状态结构体。 - 使用初始状态实例化一个
Store
。 - 使用存储实例上的
commit
方法更新状态。 - 使用
sinkState
方法订阅状态更新。
定义您的状态
创建一个代表应用程序状态的状态结构体。您的状态结构体应符合Equatable
协议。这允许Verge检测状态的变化并在必要时触发更新。
示例:
struct MyState: StateType {
var count: Int = 0
}
实例化存储
使用应用程序的初始状态创建一个Store
实例。Store
类接受两个类型参数:
- 第一个类型参数表示应用程序的状态。
- 第二个类型参数表示您想与存储一起使用的任何中间件。如果不需要任何中间件,请使用
Never
。
示例:
let store = Store<_, Never>(initialState: MyState())
更新状态
要更新应用程序状态,请使用Store
实例上的commit
方法。commit
方法接受一个闭包,该闭包有一个参数,即对状态的可变引用。在闭包内部,根据需要修改状态。
示例:
store.commit {
$0.count += 1
}
订阅状态更新
要在状态更改时接收更新,请使用Store
实例上的sinkState
方法。此方法接受一个闭包,该闭包接收更新后的状态作为其参数。每当状态发生变化时,都会调用该闭包。
示例:
store.sinkState { state in
// 接收状态的更新
}
.storeWhileSourceActive()
最后的storeWhileSourceActive()
调用是Verge提供的一种方法,用于自动管理订阅的生命周期。它会在源(在本例中为store
实例)处于活动状态时保留订阅。
就是这样!您现在了解了使用Verge在Swift应用程序中管理状态的基础知识。对于更高级的用例和其他功能,请参阅官方Verge文档和GitHub存储库。
使用存储的活动进行事件驱动编程
在某些情况下,事件驱动编程对于创建响应迅速和高效的应用程序至关重要。Verge库的存储活动功能旨在满足这一需求,允许开发人员在其项目中无缝处理事件。
当您的应用程序需要事件驱动编程时,存储的活动就派上用场了。它使您能够独立于主存储管理来管理事件和相关逻辑,促进了清晰和有组织的代码结构。这种关注点分离简化了整体开发过程,并使维护和扩展应用程序变得更加容易。
通过利用存储的活动功能,您可以在保持存储管理完整的同时高效地处理应用程序中的事件。这确保了您的应用程序保持高性能和可扩展性,使您能够使用Verge库构建强大可靠的Swift应用程序。
以下是使用存储的活动的示例:
let store: Store<MyState, MyActivity>
store.send(MyActivity.somethingHappened)
store.sinkActivity { (activity: MyActivity) in
// 处理活动
}
.storeWhileSourceActive()
在SwiftUI中使用Verge
要在SwiftUI中使用Verge,您可以利用StoreReader
在SwiftUI视图中订阅状态更新。以下是如何做到这一点的示例:
import SwiftUI
import Verge
struct ContentView: View {
@StoreObject private var viewModel = CounterViewModel()
var body: some View {
VStack {
StoreReader(viewModel.store) { state in
Text("计数: \(state.count)")
.font(.largeTitle)
}
Button(action: {
viewModel.increment()
}) {
Text("增加")
}
}
}
}
final class CounterViewModel: StoreComponentType {
struct State: Equatable {
var count: Int = 0
}
let store: Store<State, Never> = .init(initialState: .init())
func increment() {
commit {
$0.count += 1
}
}
}
在这个例子中,StoreReader
用于读取MyViewModel
存储的状态。这允许您在SwiftUI视图中访问和显示状态。此外,您可以通过直接调用存储上的方法来执行操作,如示例中的按钮所示。
这个新部分将帮助用户理解如何在SwiftUI中使用Verge,使他们能够在SwiftUI视图中有效管理状态。如果您有任何进一步的建议或更改,请告诉我!
StoreObject属性包装器:
SwiftUI提供了@StateObject
属性包装器来创建和管理遵循ObservableObject协议的给定对象的持久实例。然而,每当ObservableObject更新时,StateObject都会导致视图刷新。
在Verge中,我们引入了StoreObject属性包装器,它在视图的生命周期内实例化一个Store对象,但当Store更新时不会导致视图刷新。
当您想以更细粒度的方式管理Store,而不希望Store更改时导致整个视图刷新时,这很有用。相反,Store的更新可以通过StoreReader来处理。
在UIKit中使用Verge
以下是Verge在UIViewController中的简单使用示例:
class MyViewController: UIViewController {
private struct State: Equatable {
var count: Int = 0
}
private let store: Store<State, Never> = .init(initialState: .init())
private let label: UILabel = .init()
override func viewDidLoad() {
super.viewDidLoad()
setupUI()
// 订阅store的状态更新
store.sinkState { [weak self] state in
guard let self = self else { return }
// 使用ifChanged检查值是否已更新
state.ifChanged(\.count) { count in
self.label.text = "Count: \(count)"
}
}
.storeWhileSourceActive()
}
private func setupUI() {
// 为简洁起见省略
}
private func incrementCount() {
store.commit {
$0.count += 1
}
}
}
使用sinkState
、Changed<State>
和ifChanged
在UIKit中高效更新状态
在事件驱动的UIKit中,高效更新组件至关重要,只在需要时更新它们。Verge库提供了一种使用sinkState
方法、Changed<State>
类型和ifChanged
方法来实现这一点的方法。
当使用sinkState
方法时,您提供的闭包会接收包装在Changed<State>
类型中的最新状态。这个包装器还包含了先前的状态,允许您使用ifChanged
方法确定哪些属性已更新。
以下是在UIKit中使用sinkState
和ifChanged
高效更新组件的示例:
store.sinkState {
$0.ifChanged(\.myProperty) { newValue in
// 只有当myProperty发生变化时才更新组件
}
}
在这个例子中,只有当myProperty
发生变化时,组件才会更新,确保在基于UIKit的应用程序中高效更新。
相比之下,SwiftUI使用声明式视图结构,这意味着更新视图时较少需要检查状态变化。然而,在使用UIKit时,使用sinkState
、Changed<State>
和ifChanged
有助于保持应用程序的性能和响应性。
使用TaskManager进行异步操作
Verge的Store包含一个TaskManager,允许您调度和管理异步操作。这个功能简化了处理异步任务的过程,同时保持它们与您的Store相关联。
基本用法
要使用TaskManager,只需在Store实例上调用task
方法,并提供包含异步操作的闭包:
store.task {
await runMyOperation()
}
使用键和模式进行任务管理
TaskManager还允许您基于键和模式管理任务。您可以为每个任务分配一个唯一的键,并指定其执行模式。这使您可以根据任务的键控制任务的执行行为。
例如,您可以使用.dropCurrent
模式来丢弃任何当前正在运行的具有相同键的任务,并立即运行新任务:
store.task(key: .init("MyOperation"), mode: .dropCurrent) {
//
}
这个功能为您提供了对任务执行方式的细粒度控制,确保您的应用程序在处理多个异步操作时保持响应和高效。
高级用法:管理复杂应用程序的多个存储
理论上,在单个存储中管理整个应用程序状态是理想的。然而,在大型和复杂的应用程序中,计算复杂度可能变得很高,导致性能问题和应用程序响应缓慢。在这种情况下,建议将状态分离到多个存储中,并根据需要集成它们。
通过将状态分为多个存储,您可以减少与状态更新相关的复杂性和开销。每个存储可以管理应用程序状态的特定部分,确保更新高效快速地执行。这种方法还促进了代码的更好组织和关注点分离,使得随着时间的推移更容易维护和扩展您的应用程序。
要使用多个存储,为应用程序状态的不同部分创建单独的Store实例,然后根据需要连接它们。这可能涉及将存储实例传递给子组件或在兄弟组件之间共享存储。通过这种方式构建应用程序,您可以确保应用程序状态的每个部分都得到高效和有效的管理。
在存储之间复制状态
要在存储之间复制状态,您可以使用sinkState
方法和ifChanged
函数,仅在状态发生变化时触发更新。以下是一个示例:
store.sinkState {
$0.ifChanged(\.myState) { value in
otherStore.commit {
$0.myState = value
}
}
}
在这个例子中,当store
中的myState
状态发生变化时,新值会被提交到otherStore
。这种方法允许您高效地同步多个存储之间的状态。
使用Derived进行高效计算属性
Verge的Derived
功能允许您基于存储的状态创建计算属性,并高效地订阅更新。这个功能可以通过减少不必要的计算和更新来帮助您优化应用程序。Derived的灵感来自reselect库,并提供类似的功能。
创建派生属性
要创建派生属性,您将使用store.derived
方法。这个方法接受一个描述如何生成派生数据的Pipeline
对象:
let derived: Derived<Int> = store.derived(.select(\\.count))
您可以使用select
或map
来生成派生数据。select
用于直接从状态中获取值,而map
可以用于基于状态生成新值,类似于map函数:
let derived: Derived<Int> = store.derived(.map { $0.count * 2 })
Pipeline
会检查派生数据是否从先前的值更新。如果没有变化,Derived
不会发布任何更改。
链接派生实例
您可以从现有的 Derived 实例创建另一个 Derived 实例,有效地将它们链接在一起:
let anotherDerived: Derived<String> = derived.derived(.map { $0.description })
订阅派生属性更新
要订阅派生属性的更新,您可以使用 sinkState
方法,就像对 store 一样:
derived.sinkState { value in
// 处理派生属性的更新
}
.storeWhileSourceActive()
通过使用 Derived
处理计算属性并订阅更新,您可以确保应用程序保持高效和性能,避免不必要的计算和状态更新。
VergeNormalization 简介
状态管理在构建高效和可维护的应用程序中起着至关重要的作用。状态管理的一个重要方面是以简化数据操作和使用的方式组织数据。这就是规范化变得至关重要的原因。
规范化是一种以消除冗余并确保数据一致性的方式构建数据的过程。它在状态管理库中至关重要,因为它显著降低了操作的计算复杂性,并使状态管理变得更加容易。
文档:
让我们通过一个例子来说明规范化和非规范化数据结构之间的区别。
非规范化数据结构:
posts:
- id: 1
title: "文章 1"
author:
id: 1
name: "爱丽丝"
- id: 2
title: "文章 2"
author:
id: 1
name: "爱丽丝"
- id: 3
title: "文章 3"
author:
id: 2
name: "鲍勃"
在非规范化结构中,作者数据在每篇文章中都重复出现,这可能导致不一致性,并使状态管理变得更加困难。
规范化数据结构:
entities:
authors:
1:
id: 1
name: "爱丽丝"
2:
id: 2
name: "鲍勃"
posts:
1:
id: 1
title: "文章 1"
authorId: 1
2:
id: 2
title: "文章 2"
authorId: 1
3:
id: 3
title: "文章 3"
authorId: 2
在规范化结构中,作者数据与文章分开存储,消除了数据冗余并确保了数据一致性。文章和作者之间的关系通过文章中的 authorId
字段表示。
VergeORM 旨在有效处理状态管理库中的规范化。通过利用 VergeORM,您可以简化状态管理,降低操作的计算复杂性,并提高应用程序的整体性能和可维护性。
定义实体
以下是如何定义 Book
和 Author
实体的示例:
struct Book: EntityType {
typealias EntityIDRawType = String
var entityID: EntityID {
.init(rawID)
}
let rawID: String
var name: String = "initial"
let authorID: Author.EntityID
}
struct Author: EntityType {
typealias EntityIDRawType = String
var entityID: EntityID {
.init(rawID)
}
let rawID: String
var name: String = ""
}
定义数据库模式
要在状态中存储实体,您需要定义数据库模式:
@NormalizedStorage
struct Database {
@Table
var books: Tables.Hash<Book> = .init()
@Table
var authors: Tables.Hash<Book> = .init()
}
在状态中嵌入数据库
将 Database
嵌入到应用程序的状态中:
struct RootState: StateType {
var database: Database = .init()
}
存储和查询实体
以下是使用 store
属性存储和查询实体的示例:
// 存储实体
store.commit {
$0.database.performBatchUpdates { context in
let authors = (0..<10).map { i in
Author(rawID: "\(i)")
}
let result = context.modifying.author.insert(authors)
}
}
// 查询实体
let book = store.state.database.db.book.find(by: .init("1"))
let author = store.state.database.db.author.find(by: .init("1"))
在这个示例中,我们使用 store.commit
对数据库执行批量更新。我们向 author
实体表中插入一组新的作者。然后,我们使用 store.state.database.db
通过它们的标识符查询 book
和 author
实体。
通过使用 VergeNormalization,您可以使用规范化的数据结构高效地管理应用程序状态,这简化了状态管理,降低了操作的计算复杂性,并提高了应用程序的整体性能和可维护性。
安装
SwiftPM
Verge 支持 SwiftPM。
演示应用程序
本仓库在 Demo 目录中包含多个演示应用程序。 我们正在寻找您的演示应用程序以在此处列出! 请通过 Issue 告诉我们!
致谢
作者
许可证
Verge 基于 MIT 许可证发布。