Project Icon

swift-verge

Swift平台高效状态管理库Verge

Verge是一个为Swift平台设计的高性能状态管理库。它支持SwiftUI和UIKit,提供轻量级且易用的方法管理应用状态。Verge具备并发处理能力,提供异步操作和大量实体管理API,能有效解决大型复杂应用中的状态更新问题。该库无需复杂的操作和reducer,简化了状态管理流程。

VergeIcon

Verge.swift

📍一个适用于iOS的高效状态管理架构 - UIKit, SwiftUI📍
_ 获得单向数据流的更简单方式 _
_ 支持并发处理 _

支持这个项目

yellow-button

Verge:一个适用于SwiftUI和UIKit的高性能、可扩展的状态管理库

Verge是一个为Swift设计的高性能、可扩展的状态管理库,考虑到了真实世界的使用场景。它提供了一种轻量级且易于使用的方法来管理应用程序状态,无需复杂的操作和减速器。本指南将带您了解在Swift项目中使用Verge的基础知识。

核心概念和动机

Verge的设计考虑了以下概念:

  • 受Flux库的启发,但注重将存储模式作为核心概念。
  • 存储模式是Flux和Redux中的基本概念,专注于使用单一真实来源在组件之间共享状态。
  • Verge不规定如何管理修改状态的操作。相反,它提供了一个简单的commit函数,该函数接受描述如何更改状态的闭包。
  • 用户可以在Verge之上构建额外的层,例如实现基于枚举的操作以实现更结构化的状态管理。
  • Verge支持多线程,确保快速、安全和高效的操作。
  • 兼容UIKit和SwiftUI。
  • 包含用于处理实际应用程序开发用例的API,如管理异步操作。
  • 解决了在大型和复杂应用程序中更新状态的复杂性。
  • 提供了一个ORM,用于高效管理大量实体。
  • 为业务导向的应用程序设计。

开始使用

开始使用

要使用Verge,请按照以下步骤操作:

  1. 定义一个符合Equatable协议的状态结构体。
  2. 使用初始状态实例化一个Store
  3. 使用存储实例上的commit方法更新状态。
  4. 使用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
    }
  }
}

使用sinkStateChanged<State>ifChanged在UIKit中高效更新状态

在事件驱动的UIKit中,高效更新组件至关重要,只在需要时更新它们。Verge库提供了一种使用sinkState方法、Changed<State>类型和ifChanged方法来实现这一点的方法。

当使用sinkState方法时,您提供的闭包会接收包装在Changed<State>类型中的最新状态。这个包装器还包含了先前的状态,允许您使用ifChanged方法确定哪些属性已更新。

以下是在UIKit中使用sinkStateifChanged高效更新组件的示例:

store.sinkState {
  $0.ifChanged(\.myProperty) { newValue in
    // 只有当myProperty发生变化时才更新组件
  }
}

在这个例子中,只有当myProperty发生变化时,组件才会更新,确保在基于UIKit的应用程序中高效更新。

相比之下,SwiftUI使用声明式视图结构,这意味着更新视图时较少需要检查状态变化。然而,在使用UIKit时,使用sinkStateChanged<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))

您可以使用selectmap来生成派生数据。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,您可以简化状态管理,降低操作的计算复杂性,并提高应用程序的整体性能和可维护性。

定义实体

以下是如何定义 BookAuthor 实体的示例:

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 通过它们的标识符查询 bookauthor 实体。

通过使用 VergeNormalization,您可以使用规范化的数据结构高效地管理应用程序状态,这简化了状态管理,降低了操作的计算复杂性,并提高了应用程序的整体性能和可维护性。

安装

SwiftPM

Verge 支持 SwiftPM。

演示应用程序

本仓库在 Demo 目录中包含多个演示应用程序。 我们正在寻找您的演示应用程序以在此处列出! 请通过 Issue 告诉我们!

致谢

作者

🇯🇵 Muukii (Hiroshi Kimura)

许可证

Verge 基于 MIT 许可证发布。

项目侧边栏1项目侧边栏2
推荐项目
Project Cover

豆包MarsCode

豆包 MarsCode 是一款革命性的编程助手,通过AI技术提供代码补全、单测生成、代码解释和智能问答等功能,支持100+编程语言,与主流编辑器无缝集成,显著提升开发效率和代码质量。

Project Cover

AI写歌

Suno AI是一个革命性的AI音乐创作平台,能在短短30秒内帮助用户创作出一首完整的歌曲。无论是寻找创作灵感还是需要快速制作音乐,Suno AI都是音乐爱好者和专业人士的理想选择。

Project Cover

有言AI

有言平台提供一站式AIGC视频创作解决方案,通过智能技术简化视频制作流程。无论是企业宣传还是个人分享,有言都能帮助用户快速、轻松地制作出专业级别的视频内容。

Project Cover

Kimi

Kimi AI助手提供多语言对话支持,能够阅读和理解用户上传的文件内容,解析网页信息,并结合搜索结果为用户提供详尽的答案。无论是日常咨询还是专业问题,Kimi都能以友好、专业的方式提供帮助。

Project Cover

阿里绘蛙

绘蛙是阿里巴巴集团推出的革命性AI电商营销平台。利用尖端人工智能技术,为商家提供一键生成商品图和营销文案的服务,显著提升内容创作效率和营销效果。适用于淘宝、天猫等电商平台,让商品第一时间被种草。

Project Cover

吐司

探索Tensor.Art平台的独特AI模型,免费访问各种图像生成与AI训练工具,从Stable Diffusion等基础模型开始,轻松实现创新图像生成。体验前沿的AI技术,推动个人和企业的创新发展。

Project Cover

SubCat字幕猫

SubCat字幕猫APP是一款创新的视频播放器,它将改变您观看视频的方式!SubCat结合了先进的人工智能技术,为您提供即时视频字幕翻译,无论是本地视频还是网络流媒体,让您轻松享受各种语言的内容。

Project Cover

美间AI

美间AI创意设计平台,利用前沿AI技术,为设计师和营销人员提供一站式设计解决方案。从智能海报到3D效果图,再到文案生成,美间让创意设计更简单、更高效。

Project Cover

AIWritePaper论文写作

AIWritePaper论文写作是一站式AI论文写作辅助工具,简化了选题、文献检索至论文撰写的整个过程。通过简单设定,平台可快速生成高质量论文大纲和全文,配合图表、参考文献等一应俱全,同时提供开题报告和答辩PPT等增值服务,保障数据安全,有效提升写作效率和论文质量。

投诉举报邮箱: service@vectorlightyear.com
@2024 懂AI·鲁ICP备2024100362号-6·鲁公网安备37021002001498号