{"id":18131539,"url":"https://github.com/vergegroup/swift-verge","last_synced_at":"2025-04-04T09:09:08.408Z","repository":{"id":39919032,"uuid":"107319016","full_name":"VergeGroup/swift-verge","owner":"VergeGroup","description":"🟣 A robust Swift state-management framework designed for complex applications, featuring an integrated ORM for efficient data handling.","archived":false,"fork":false,"pushed_at":"2024-05-07T06:48:57.000Z","size":79998,"stargazers_count":620,"open_issues_count":6,"forks_count":33,"subscribers_count":12,"default_branch":"main","last_synced_at":"2024-05-13T15:27:54.395Z","etag":null,"topics":["data-driven","dispatcher","flux","ios","memoization","mvvm","performance-monitoring","redux","rxswift","state-shape","state-tree","store-pattern","swift","swiftui","uikit","verge","vuex"],"latest_commit_sha":null,"homepage":"https://swiftpackageindex.com/VergeGroup/swift-Verge/main/documentation/verge","language":"Swift","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/VergeGroup.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":".github/FUNDING.yml","license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null},"funding":{"github":["muukii"],"patreon":"muukii","ko_fi":"muukii"}},"created_at":"2017-10-17T20:15:00.000Z","updated_at":"2024-07-13T12:40:18.522Z","dependencies_parsed_at":"2022-09-13T22:51:17.929Z","dependency_job_id":"ab024a32-24bd-431a-9cb2-9169fb65d463","html_url":"https://github.com/VergeGroup/swift-verge","commit_stats":{"total_commits":1518,"total_committers":10,"mean_commits":151.8,"dds":0.04150197628458496,"last_synced_commit":"af0d6f8103b63ddfe96c82f88ccf20db5510ab8d"},"previous_names":["muukii/verge","muukii/cycler","vergegroup/verge"],"tags_count":167,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/VergeGroup%2Fswift-verge","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/VergeGroup%2Fswift-verge/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/VergeGroup%2Fswift-verge/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/VergeGroup%2Fswift-verge/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/VergeGroup","download_url":"https://codeload.github.com/VergeGroup/swift-verge/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247149502,"owners_count":20891954,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["data-driven","dispatcher","flux","ios","memoization","mvvm","performance-monitoring","redux","rxswift","state-shape","state-tree","store-pattern","swift","swiftui","uikit","verge","vuex"],"created_at":"2024-11-01T12:07:59.735Z","updated_at":"2025-04-04T09:09:08.380Z","avatar_url":"https://github.com/VergeGroup.png","language":"Swift","funding_links":["https://github.com/sponsors/muukii","https://patreon.com/muukii","https://ko-fi.com/muukii","https://www.buymeacoffee.com/muukii"],"categories":[],"sub_categories":[],"readme":"\u003e 📢👨🏻 v14 is comming\n\n\u003cp align=\"center\"\u003e\n  \u003ca href=\"https://vergegroup.org\"\u003e\n\u003cimg width=\"128\" alt=\"VergeIcon\" src=\"https://user-images.githubusercontent.com/1888355/85241314-5ac19c80-b476-11ea-9be6-e04ed3bc6994.png\"\u003e\n    \u003c/a\u003e\n\u003c/p\u003e\n\n\u003ch1 align=\"center\"\u003eVerge.swift\u003c/h1\u003e\n\u003cp align=\"center\"\u003e\n  \u003cb\u003e📍An effective state management architecture for iOS - UIKit, SwiftUI📍\u003c/b\u003e\u003cbr/\u003e\n\u003csub\u003e_ An easier way to get unidirectional data flow _\u003c/sub\u003e\u003cbr/\u003e\n\u003csub\u003e_ Supports concurrent processing _\u003c/sub\u003e\u003cbr/\u003e\n\u003c/p\u003e\n\n[My development note](https://www.notion.so/muukii/Verge-93813aec77d44bef802a2d92f565c7bb?pvs=4)\n\n## Using StoreReader or @Reading in SwiftUI\n\nIn SwiftUI, there are two ways to observe a Store: using the `StoreReader` view or the `@Reading` property wrapper. Both efficiently track state changes and only re-render the view when tracked values change.\n\nFirst, define your state with `@Tracking` macro:\n\n```swift\n@Tracking\nstruct State {\n  var count: Int = 0\n\n  @Tracking\n  struct NestedState {\n    var isActive: Bool = false\n    var message: String = \"Hello, Verge!\"\n  }\n\n  var nestedState: NestedState = NestedState()\n}\n```\n\n### StoreReader Example\n\n```swift\nstruct MyView: View {\n  let store = Store\u003cState, Never\u003e(initialState: .init())\n  \n  var body: some View {\n    StoreReader(store) { state in\n      VStack {\n        Text(\"Count: \\(state.count)\")\n        Button(\"Increment\") {\n          store.commit {\n            $0.count += 1\n          }\n        }\n        Text(\"Is Active: \\(state.nestedState.isActive)\")\n        Text(\"Message: \\(state.nestedState.message)\")\n        Button(\"Toggle Active\") {\n          store.commit {\n            $0.nestedState.isActive.toggle()\n          }\n        }\n      }\n    }\n  }\n}\n```\n\n### @Reading Example\n\n```swift\nstruct MyView: View {\n  @Reading\u003cStore\u003cState, Never\u003e\u003e var state: State\n  \n  init() {\n    self._state = .init(\n      label: \"MyView\",\n      { Store\u003cState, Never\u003e(initialState: .init()) }\n    )\n  }\n  \n  var body: some View {\n    VStack {\n      Text(\"Count: \\(state.count)\")\n      Button(\"Increment\") {\n        $state.commit {\n          $0.count += 1\n        }\n      }\n      Text(\"Is Active: \\(state.nestedState.isActive)\")\n      Text(\"Message: \\(state.nestedState.message)\")\n      Button(\"Toggle Active\") {\n        $state.commit {\n          $0.nestedState.isActive.toggle()\n        }\n      }\n    }\n  }\n}\n```\n\n---\n\n- Dependencies\n  - [TypedIdentifier](https://github.com/VergeGroup/TypedIdentifier)\n  - [TypedComparator](https://github.com/VergeGroup/TypedComparator)\n  - [Normalization](https://github.com/VergeGroup/Normalization)\n\n- Docs\n  - [Verge](https://swiftpackageindex.com/VergeGroup/swift-Verge/main/documentation/verge)\n  - [VergeNormalizationDerived](https://swiftpackageindex.com/VergeGroup/swift-Verge/main/documentation/vergenormalizationderived)\n\n## Support this projects\n\u003ca href=\"https://www.buymeacoffee.com/muukii\"\u003e\n\u003cimg width=\"160\" alt=\"yellow-button\" src=\"https://user-images.githubusercontent.com/1888355/146226808-eb2e9ee0-c6bd-44a2-a330-3bbc8a6244cf.png\"\u003e\n\u003c/a\u003e\n\n# Verge: A High-Performance, Scalable State Management Library for SwiftUI and UIKit\n\nVerge is a high-performance, scalable state management library for Swift, designed with real-world use cases in mind. It offers a lightweight and easy-to-use approach to managing your application state without the need for complex actions and reducers. This guide will walk you through the basics of using Verge in your Swift projects.\n\n## Key Concepts and Motivations\n\nVerge was designed with the following concepts in mind:\n\n- Inspired by the Flux library, but with a focus on providing a store-pattern as the core concept.\n- The store-pattern is a primitive concept found in Flux and Redux, focusing on sharing state between components using a single source of truth.\n- Verge does not dictate how to manage actions to modify the state. Instead, it provides a simple `commit` function that accepts a closure describing how to change the state.\n- Users can build additional layers on top of Verge, such as implementing enum-based actions for more structured state management.\n- Verge supports multi-threading, ensuring fast, safe, and efficient operation.\n- Compatible with both UIKit and SwiftUI.\n- Includes APIs for handling real-world application development use cases, such as managing asynchronous operations.\n- Addresses the complexity of updating state in large and complex applications.\n- Provides an ORM for efficient management of a large number of entities.\n- Designed for use in business-focused applications.\n\n## Getting Started\n\nTo use Verge, follow these steps:\n\n1. Define a state struct with `@Tracking` macro\n2. Instantiate a `Store` with your initial state\n3. Update the state using the `commit` method on the store instance\n4. Subscribe to state updates using the `sinkState` method\n\n\n## Defining Your State\n\nCreate a state struct that represents the state of your application. Use the `@Tracking` macro to make your state trackable by Verge. This allows Verge to detect changes in your state and trigger updates as necessary.\n\n```swift\n@Tracking\nstruct MyState {\n  var count: Int = 0 \n}\n```\n\n## Instantiating a Store\n\nCreate a `Store` instance with the initial state of your application. The `Store` class takes two type parameters:\n\n- The first type parameter represents the state of your application.\n- The second type parameter represents any activity you want to use with your store. If you don't need any activity, use `Never`.\n\n```swift\nlet store = Store\u003c_, Never\u003e(initialState: MyState())\n```\n\n## Updating the State\n\nTo update your application state, use the `commit` method on your `Store` instance. The `commit` method takes a closure with a single parameter, which is a mutable reference to your state. Inside the closure, modify the state as needed.\n\n```swift\nstore.commit {   \n  $0.count += 1 \n}\n```\n\n## Subscribing to State Updates\n\nTo receive updates when the state changes, use the `sinkState` method on your `Store` instance. This method takes a closure that receives the updated state as its parameter. The closure will be called whenever the state changes.\n\n```swift\nstore.sinkState { state in\n  // Receives updates of the state\n}\n.storeWhileSourceActive()\n```\n\nThe `storeWhileSourceActive()` call at the end is a method provided by Verge to automatically manage the lifetime of the subscription. It retains the subscription as long as the source (in this case, the `store` instance) is alive.\n\n## Using Activity of Store for Event-Driven Programming\n\nIn certain scenarios, event-driven programming is essential for creating responsive and efficient applications. The Verge library's Activity of Store feature is designed to cater to this need, allowing developers to handle events seamlessly within their projects.\n\nThe Activity of Store comes into play when your application requires event-driven programming. It enables you to manage events and associated logic independently from the main store management, promoting a clean and organized code structure. This separation of concerns simplifies the overall development process and makes it easier to maintain and extend your application over time.\n\nBy leveraging the Activity of Store functionality, you can efficiently handle events within your application while keeping the store management intact. This ensures that your application remains performant and scalable, enabling you to build robust and reliable Swift applications using the Verge library.\n\nHere's an example of using Activity of Store:\n\n```swift\nlet store: Store\u003cMyState, MyActivity\u003e\n\nstore.send(MyActivity.somethingHappened)\n```\n\n```swift\nstore.sinkActivity { (activity: MyActivity) in\n  // handle activities.\n}\n.storeWhileSourceActive()\n```\n\n## Using Verge with SwiftUI\n\nTo use Verge in SwiftUI, you can utilize the `StoreReader` to subscribe to state updates within your SwiftUI views. Here's an example of how to do this:\n\n```swift\nimport SwiftUI\nimport Verge\n\nstruct ContentView: View {\n  @StoreObject private var viewModel = CounterViewModel()\n\n  var body: some View {\n    VStack {\n      StoreReader(viewModel) { state in\n        Text(\"Count: \\(state.count)\")\n          .font(.largeTitle)\n      }\n\n      Button(action: {\n        viewModel.increment()\n      }) {\n        Text(\"Increment\")\n      }\n    }\n  }\n}\n\nfinal class CounterViewModel: StoreComponentType {\n  @Tracking\n  struct State {\n    var count: Int = 0\n  }\n\n  let store: Store\u003cState, Never\u003e = .init(initialState: .init())\n\n  func increment() {\n    commit {\n      $0.count += 1\n    }\n  }\n}\n```\n\nIn this example, `StoreReader` is used to read the state from the `MyViewModel` store. This allows you to access and display the state within your SwiftUI view. Additionally, you can perform actions by calling methods on the store directly, as demonstrated with the button in the example.\n\nThis new section will help users understand how to use Verge with SwiftUI, allowing them to manage state effectively within their SwiftUI views. Let me know if you have any further suggestions or changes!\n\n**StoreObject** property wrapper:\n\nSwiftUI provides the `@StateObject` property wrapper to create and manage a persistent instance of a given object that adheres to the ObservableObject protocol. However, StateObject will cause the view to be refreshed whenever the ObservableObject is updated.\n\nIn Verge, we introduce the StoreObject property wrapper, which instantiates a Store object for the duration of the view's lifecycle but does not cause the view to refresh when the Store updates.\n\nThis is beneficial when you want to manage the Store in a more granular way, without causing the entire view to refresh when the Store changes. Instead, Store updates can be handled through the StoreReader.\n\n## Using Verge with UIKit\n\nHere's a simple usage example of Verge with a UIViewController:\n\n```swift\nclass MyViewController: UIViewController {\n  \n  @Tracking\n  private struct State {\n    var count: Int = 0\n  }\n  \n  private let store: Store\u003cState, Never\u003e = .init(initialState: .init())\n  \n  private let label: UILabel = .init()\n  \n  override func viewDidLoad() {\n    super.viewDidLoad()\n    \n    setupUI()\n    \n    // Subscribe to the store's state updates\n    store.sinkState { [weak self] state in\n      guard let self = self else { return }\n      \n      // Check if the value has been updated using ifChanged\n      state.ifChanged(\\.count) { count in\n        self.label.text = \"Count: \\(count)\"\n      }\n    }\n    .storeWhileSourceActive()\n  }\n  \n  private func setupUI() {\n    // Omitted for brevity\n  }\n  \n  private func incrementCount() {\n    store.commit {\n      $0.count += 1\n    }\n  }\n}\n```\n\n## Efficient State Updates in UIKit using `sinkState`, `Changed\u003cState\u003e`, and `ifChanged`\n\nIn UIKit, which is event-driven, it's crucial to update components efficiently by only updating them as needed. The Verge library provides a way to achieve this using the `sinkState` method, the `Changed\u003cState\u003e` type, and the `ifChanged` method.\n\nWhen you use the `sinkState` method, the closure you provide receives the latest state wrapped in a `Changed\u003cState\u003e` type. This wrapper also includes the previous state, allowing you to determine which properties have been updated using the `ifChanged` method.\n\nHere's an example of using `sinkState` and `ifChanged` in UIKit to efficiently update components:\n\n```swift\nstore.sinkState {\n  $0.ifChanged(\\.myProperty) { newValue in\n    // Update the component only when myProperty has changed\n  }\n}\n```\n\nIn this example, the component is updated only when `myProperty` has changed, ensuring efficient updates in the UIKit-based application.\n\nCompared to UIKit, SwiftUI works with a declarative view structure, which means that there is less need to check for state changes to update the view. However, when working with UIKit, using `sinkState`, `Changed\u003cState\u003e`, and `ifChanged` helps maintain a performant and responsive application.\n\n## Using TaskManager for Asynchronous Operations\n\nVerge's Store includes a TaskManager that allows you to dispatch and manage asynchronous operations. This feature simplifies handling async tasks while keeping them associated with your Store.\n\n### Basic usage\n\nTo use TaskManager, simply call the `task` method on your Store instance, and provide a closure that contains the asynchronous operation:\n\n```swift\nstore.task {\n  await runMyOperation()\n}\n```\n\n### Task management with keys and modes\n\nTaskManager also enables you to manage tasks based on keys and modes. You can assign a unique key to each task and specify a mode for its execution. This allows you to control the execution behavior of tasks based on their keys.\n\nFor example, you can use the `.dropCurrent` mode to drop any currently running tasks with the same key and run the new task immediately:\n\n```swift\nstore.task(key: .init(\"MyOperation\"), mode: .dropCurrent) {\n  //\n}\n```\n\nThis functionality provides you with fine-grained control over how tasks are executed, ensuring that your application remains responsive and efficient, even when handling multiple asynchronous operations.\n\n## Advanced Usage: Managing Multiple Stores for Complex Applications\n\nIn theory, managing your entire application state in a single store is ideal. However, in large and complex applications, the computational complexity can become significant, leading to performance issues and slow application responsiveness. In such cases, it's recommended to separate your state into multiple stores and integrate them as needed.\n\nBy dividing your state into multiple stores, you can reduce the complexity and overhead associated with state updates. Each store can manage a specific part of your application state, ensuring that updates are performed efficiently and quickly. This approach also promotes better organization and separation of concerns in your code, making it easier to maintain and extend your application over time.\n\nTo use multiple stores, create separate Store instances for different parts of your application state, and then connect them as needed. This may involve passing store instances to child components or sharing stores between sibling components. By structuring your application this way, you can ensure that each part of your application state is managed efficiently and effectively.\n\n### Copying State Between Stores\n\nTo copy state between stores, you can use the `sinkState` method along with the `ifChanged` function to only trigger updates when the state has changed. Here's an example:\n\n```swift\nstore.sinkState {\n  $0.ifChanged(\\.myState) { value in\n    otherStore.commit {\n      $0.myState = value\n    }\n  }\n}\n```\n\nIn this example, when the state of `myState` changes in `store`, the new value is committed to `otherStore`. This approach allows you to synchronize state between multiple stores efficiently.\n\n## Using Derived for Efficient Computed Properties\n\nVerge's `Derived` feature allows you to create computed properties based on your store's state and efficiently subscribe to updates. This feature can help you optimize your application by reducing unnecessary computations and updates. Derived is inspired by the [reselect](https://github.com/reduxjs/reselect) library and provides similar functionality.\n\n### Creating a Derived Property\n\nTo create a derived property, you'll use the `store.derived` method. This method takes a `Pipeline` object that describes how the derived data is generated:\n\n```swift\nlet derived: Derived\u003cInt\u003e = store.derived(.select(\\\\.count))\n```\n\nYou can use `select` or `map` to generate derived data. `select` is used to take a value directly from the state, while `map` can be used to generate new values based on the state, similar to a map function:\n\n```swift\nlet derived: Derived\u003cInt\u003e = store.derived(.map { $0.count * 2 })\n```\n\nThe `Pipeline` checks if the derived data has been updated from the previous value. If it hasn't changed, `Derived` won't publish any changes.\n\n### Chaining Derived Instances\n\nYou can create another Derived instance from an existing Derived instance, effectively chaining them together:\n\n```swift\nlet anotherDerived: Derived\u003cString\u003e = derived.derived(.map { $0.description })\n```\n\n### Subscribing to Derived Property Updates\n\nTo subscribe to updates of a derived property, you can use the `sinkState` method, just like with a store:\n\n```swift\nderived.sinkState { value in \n  // Handle updates of the derived property \n} \n.storeWhileSourceActive()\n```\n\nBy using `Derived` for computed properties and subscribing to updates, you can ensure that your application remains efficient and performant, avoiding unnecessary computations and state updates.\n\n# Normalization\n\nVergeGroup and Verge package provide a library for normalization techniques to handle entities in an efficient way.  \n[Normalization](https://github.com/VergeGroup/Normalization) is a library that makes tables in a struct and manages value-type entities in copy-efficient tables.  \nIt can be used in the same way as handling value types. Which means we can use it with Verge bringing them into store-pattern.  \n`VergeNormalizationDerived` target provides functionalities for subscribing entity updates when it's using with Verge.\n\n# Installation\n\n## SwiftPM\n\nVerge supports SwiftPM.\n\n## Thanks\n\n- [Redux](https://redux.js.org/)\n- [Vuex](https://vuex.vuejs.org/)\n- [ReSwift](https://github.com/ReSwift/ReSwift)\n- [swift-composable-architecture](https://github.com/pointfreeco/swift-composable-architecture)\n\n## Author\n\n[🇯🇵 Muukii (Hiroshi Kimura)](https://github.com/muukii)\n\n## License\n\nVerge is released under the MIT license.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fvergegroup%2Fswift-verge","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fvergegroup%2Fswift-verge","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fvergegroup%2Fswift-verge/lists"}