futuredapp / futuredapp/FuturedKit

Enhancement: Add DataCache.observations(of:) for per-property observation

Open
#55 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
Swift
Stars
3
Forks
0
Avg merge
2d 23h
Merged PRs (30d)
2

Description

Future Enhancement: Swift 6.2+ & Per-Property Observation & Task.immediate

Motivation

The current imperative observation pattern documented for DataCache uses a raw withObservationTracking loop on dataCache.value. Because DataCache has a single stored property (value), this re-fires on any property change of the model, not just the one the subscriber cares about.

Additionally, spawning async work from SwiftUI's .task {} modifier introduces an invisible suspension before the first execution, which can cause a frame gap before the initial observation tracking is registered.

Proposed Changes
1. Add DataCache.observations(of:)

Add a keyPath-scoped observation method to DataCache:

@available(iOS 26, macOS 26, watchOS 26, tvOS 26, *)
@MainActor
public func observations<T: Equatable>(
    of keyPath: KeyPath<Model, T>
) -> some AsyncSequence<T, Never> {
    Observations { self.value[keyPath: keyPath] }
}

This gives per-property granularity: the sequence yields only when the observed property changes, not when any other part of the model changes. This is the native Swift Concurrency equivalent of Combine's .map(\.property).removeDuplicates().

Typical call site in a ComponentModel:

func onAppear() async {
    for await items in dataCache.observations(of: \.items) {
        processItems(items) // called only when items changes
    }
}

Additional considerations:

  • Value buffering: Observations can skip intermediate values if the consumer is slower than the producer (Donny Wals). For UI-driving state this is fine (you always want the latest value), but this should be documented for users who might expect every intermediate value.
  • Did-set semantics: Unlike the old withObservationTracking (will-set), Observations delivers values after mutation. This is actually better for consumers reading the final state.
  • Equatable constraint: The T: Equatable constraint provides an extra deduplication layer beyond the transactional coalescing that Observations already provides. This prevents re-emission when a transaction sets a property to the same value it already had.
2. Consider Task.immediate in .task modifier (with caveats)

Since DataCache and ComponentModel are both @MainActor, using Task.immediate (SE-0472) in the SwiftUI .task {} modifier ensures the first execution starts synchronously without a suspension gap, so the initial observation tracking is registered immediately:

.task {
    Task.immediate {
        await model.onAppear()
    }
}

⚠️ Important caveat — cancellation:

Task.immediate creates an unstructured task inside SwiftUI's structured .task scope. When the view disappears, .task cancels its body, but the inner Task.immediate will not be automatically cancelled. The returned Task handle must be managed manually if cancellation is needed:

.task {
    let task = Task.immediate {
        await model.onAppear()
    }
    // Wait for it within the structured scope so cancellation propagates
    await withTaskCancellationHandler {
        await task.value
    } onCancel: {
        task.cancel()
    }
}

Recommendation: Rather than recommending Task.immediate as the default pattern, document it as an optimization for cases where the initial suspension gap is a measurable problem (e.g., flicker on initial load). The Swift Forums discussion clarifies that Task.immediate is intended for ordering guarantees within the same isolation domain, not as a general-purpose "run sync" tool.

Contributor guide

No contributing guide indexed for this repository

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start by locating DataCache and the documented withObservationTracking usage, then review ComponentModel.onAppear and SwiftUI .task call sites. Check the proposed key-path observation API against Swift 6.2 availability and the cancellation caveat for Task.immediate. Done means the scope of the implementation and documentation changes is settled and covered by appropriate tests.

Written by the indexing model from the issue text.

Assessment

Tech stack
swift
Domain
mobile-dev
Issue type
Feature
Difficulty
4/5
Estimated time
3-5 days
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
38/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.