Swift Identified Collections

repository·main·Indexed 20 days ago

https://github.com/pointfreeco/swift-identified-collections

A library providing specialized data structures, primarily IdentifiedArray, for managing collections of identifiable elements. It replaces standard Arrays in application state to provide safe, stable, and performant mutations via id-based subscripts, avoiding the pitfalls of index-based mutation in asynchronous workflows. Built as a wrapper around Apple's OrderedDictionary, it integrates with SwiftUI's List and ForEach and does not require elements to conform to Hashable.

Tokens
2K
Snippets
5
Records
7
Agent score
20%

What's inside swift-identified-collections

  1. How IdentifiedArray works and its design constraints

    main

    IdentifiedArray is a lightweight wrapper around Apple's OrderedDictionary. It is specifically designed to maintain the invariants required for a collection of identifiable elements.

    Design Principles

    • Invariant Protection: Unlike a raw OrderedDictionary<ID, Element>, IdentifiedArray ensures that an element's identifier always matches its key and prevents duplicate IDs.
    • No Hashable Requirement: Unlike OrderedSet, IdentifiedArray does not require the Element to conform to Hashable.
    • Flexible Identification: Elements do not strictly need to conform to Identifiable; you can construct an IdentifiedArray by providing an id key path.

    Performance

    IdentifiedArray is designed to match the performance characteristics of OrderedDictionary.

  2. What are identified collections and when to use them

    main

    Identified collections are data structures designed for working with collections of identifiable elements in an ergonomic and performant way.

    Standard Array types can be problematic when modeling application state because:

    1. Mutation via index is unsafe: Indices are not stable. If an element is moved or removed during an asynchronous operation, using a previously captured index can lead to mutating the wrong element or crashing the application.
    2. Mutation via ID is slow: Finding an element by its ID in a standard array requires a full traversal ($O(n)$).
    3. SwiftUI Integration: Passing enumerated collections to List or ForEach can lead to unnecessary array allocations in the view body.

    IdentifiedArray solves these issues by allowing you to mutate elements directly via their stable identifiers using an id-based subscript, ensuring both performance and safety even after asynchronous work.

    import IdentifiedCollections
    
    class TodosViewModel: ObservableObject {
      @Published var todos: IdentifiedArrayOf<Todo> = []
      
      func todoCheckboxToggled(at id: Todo.ID) async {
        // Mutate directly via ID safely and performantly
        self.todos[id: id]?.isComplete.toggle()
        
        do {
          // Even after async work, the ID remains a stable way to update the correct element
          self.todos[id: id] = try await self.apiClient.updateTodo(self.todos[id: id]!)
        } catch {
          // Handle error
        }
      }
    }
  3. How IdentifiedArray is designed

    main

    Implementation Details

    IdentifiedArray is a lightweight wrapper around Apple's OrderedDictionary from the swift-collections package. It is optimized for holding collections of identifiable elements in application state.

    Key Design Guarantees

    • Invariant Protection: Unlike a raw OrderedDictionary<ID, Element>, IdentifiedArray prevents situations where an element's identifier does not match its key or where multiple values share the same ID.
    • Flexible Requirements: Unlike OrderedSet, IdentifiedArray does not require the Element to conform to Hashable. It also does not strictly require Identifiable conformance if a key path is provided during initialization.
    • SwiftUI Compatibility: It integrates seamlessly with SwiftUI's List and ForEach views.
  4. Use IdentifiedArray for safe and performant mutations

    main

    You can replace a standard Array with IdentifiedArrayOf<Element> in your view models. This allows you to use an id-based subscript [id: id] to access and mutate elements directly. This is particularly useful for avoiding the pitfalls of index-based mutation in asynchronous workflows.

    import IdentifiedCollections
    
    struct Todo: Identifiable {
      let id: UUID
      var isComplete = false
    }
    
    class TodosViewModel: ObservableObject {
      @Published var todos: IdentifiedArrayOf<Todo> = []
    
      func todoCheckboxToggled(at id: Todo.ID) async {
        // 1. Direct mutation via ID
        self.todos[id: id]?.isComplete.toggle()
        
        do {
          // 2. Safe update after async work using the same ID
          if let todo = self.todos[id: id] {
            self.todos[id: id] = try await self.apiClient.updateTodo(todo)
          }
        } catch {
          // Handle error
        }
      }
    }
  5. Use IdentifiedArray for managing identifiable state

    main

    Instead of using a standard Array to hold identifiable elements, use IdentifiedArray. This allows you to mutate elements directly via their id using an id-based subscript, which is safer and more performant than using indices, especially when performing asynchronous work.

    Key Benefits

    • Stable Identifiers: Unlike indices, IDs remain stable even if the collection is reordered or elements are removed.
    • Ergonomic Mutation: You can access and update elements directly via todos[id: id] without manual array traversals.
    • SwiftUI Integration: IdentifiedArray works seamlessly with SwiftUI views like List and ForEach.
    import IdentifiedCollections
    
    struct Todo: Identifiable {
      var description = ""
      let id: UUID
      var isComplete = false
    }
    
    class TodosViewModel: ObservableObject {
      @Published var todos: IdentifiedArrayOf<Todo> = []
    
      func todoCheckboxToggled(at id: Todo.ID) async {
        // Mutate directly via ID
        self.todos[id: id]?.isComplete.toggle()
        
        do {
          // Update via ID even after async work
          if let todo = self.todos[id: id] {
            self.todos[id: id] = try await self.apiClient.updateTodo(todo)
          }
        } catch {
          // Handle error
        }
      }
    }
    
    // In SwiftUI
    List(self.viewModel.todos) { todo in
      // ...
    }
  6. Install Identified Collections

    main

    You can add Identified Collections to your project using either Xcode or Swift Package Manager (SwiftPM).

    Xcode

    Add the repository as a package dependency in your Xcode project: https://github.com/pointfreeco/swift-identified-collections

    Swift Package Manager

    Add the following dependency to your Package.swift file:

    dependencies: [
      .package(url: "https://github.com/pointfreeco/swift-identified-collections", from: "0.5.0")
    ],