7.5. Swift¶
7.5.1. Snapshot¶
- Install:
.package(url: "https://github.com/thekaveh/VMx.git", from: "3.24.0") - Current source: Swift 3.26.0 implementing spec 3.25.0
- Publication status: 3.24.0 is public through the immutable
v3.24.0SwiftPM tag and matchingswift-v3.24.0GitHub Release. - Reactive primitive:
Combine - Naming idiom: camelCase
- Hub delivery: callbacks run outside the state condition; ordinary concurrent producers remain synchronous and nested cross-hub sends cannot deadlock
- Notification replay: pending subscribers are registered atomically, then
attached outside the hub lock before
CurrentValueSubjectreplays state - Modal completion:
BasicModalVMatomically registers waiters and accepts only the first concurrent dismissal or disposal result
7.5.2. What To Reach For¶
Swift is the right fit when you want the same VMx lifecycle and conformance surface in a SwiftPM package, with SwiftUI adapters kept outside the core library boundary.
Use .product(name: "VMx", package: "vmx") in the consuming target. The
lowercase package value is SwiftPM's canonical identity for the repository;
the imported module and product remain VMx.
7.5.3. Serviced Collections¶
ServicedObservableCollection<T> publishes locally through Combine, then to
an optional external hub:
let notes = ServicedObservableCollection<Note>(hub: hub)
let changes = notes.collectionChanged.sink { message in render(message) }
notes.append(first)
notes.append(second)
notes.replace(at: 0, with: revised) // setAt remains available
try notes.move(from: 0, to: notes.count - 1)
notes.replaceAll(serverSnapshot) // one Reset
Value removal is available when T: Equatable, removes the first match, and
returns false when absent. removeAt and replace retain Swift's established
array-precondition bounds behavior; move instead throws the catchable
VMCollectionIndexError. Equal-index move and empty clear are no-ops. The
caller owns both the Combine cancellable and every stored item.
Use KeyedServicedObservableCollection<Key,T> for captured-key access while
retaining the same ordered message contract:
let notesByID = KeyedServicedObservableCollection<String, Note>(
keyOf: { $0.id },
hub: hub
)
try notesByID.append(first)
let note = notesByID.get(first.id)
let added = try notesByID.upsert(revised) // false: Replace at stable position
let removed = notesByID.delete(first.id)
containsKey tests membership. The projector is throwing, so append,
replacement, whole-list replacement, and upsert are throwing and atomic.
Captured membership keys do not follow mutable properties; indexed replacement
or delete-then-add rekeys explicitly. The same mutated instance can occupy its
old and newly projected memberships. Duplicate/projector failure preserves
state and emits nothing. Lookup and target discovery are expected O(1), append
is amortized O(1), and ordered middle shifts remain O(n). Local Combine
delivery stays immediate when an external hub transaction defers only hub
publication. Items remain caller-owned; the keyed type adds no batch or VM
lifecycle interface.
7.5.4. Imperative Engine Bridge¶
The Equatable overload of subscribeValue uses ==; the isEqual: overload
accepts custom equality without an Equatable constraint. Both return
AnyCancellable:
import Combine
import VMx
let exposureSubscription: AnyCancellable = try subscribeValue(
cameraVM,
selector: { $0.model.exposure },
callback: { exposure, _ in
material.uniforms.exposure.value = exposure
},
fireImmediately: true
)
// Host adapter disposal:
exposureSubscription.cancel()
The callback receives (current, previous); immediate delivery uses the
initial value for both. The host adapter owns the cancellable, and the selector
reevaluates after every property message from this fixed VM rather than on
every render frame.
7.5.5. Pointers¶
- Flavor README: langs/swift/README.md
- Getting started guide: Getting Started with VMx — Swift
- Example portfolio: Examples overview
- Flagship Notes Workspace: Notes Workspace
- SwiftUI recipe: SwiftUI Integration
7.5.6. Current Example Coverage¶
- SwiftUI flagship:
examples/swift/notes-showcase/
The Swift flavor is at full library parity. Its current example surface is narrower than the other languages, but the flagship README points to the same cross-flavor scenario contract and parity matrix.
7.5.7. Coverage¶
The coverage floor job in .github/workflows/swift.yml runs the library tests
once with code coverage on macos-15 and the default Xcode. It fails when line,
region, or function coverage falls below the Swift floors in
tools/coverage-floors.json:
swift test --package-path langs/swift --enable-code-coverage \
-Xswiftc -Xllvm -Xswiftc -instrprof-atomic-counter-update-all
python3 tools/check-coverage-floor.py --flavor swift \
--report "$(swift test --package-path langs/swift --show-codecov-path)"
The denominator is langs/swift/Sources/VMx/ only. Test files, the bundled
JSON resources, and build output are not counted, so a change that touches only
generated or resource files cannot fail the gate. Removing a test that exercised
library code lowers the figure and can. A failure names each source file that
gained uncovered lines since the recorded baseline, with that file's unexecuted
line ranges as llvm-cov show marks them. The job uploads the llvm-cov export
and a provenance file that names the commit, toolchain, and measured figures.
Instrumentation stays in that job's debug build; the release builds, platform
builds, and packages never see it.
The coverage build updates its counters atomically. Coverage counts the code
after a guard as the function's entries minus its early returns, and plain
counter updates can lose entries when a test calls one function from many
threads at once, as DISP-003 does with NotificationHub.dispose(). A lost entry
can make a region that ran read as unexecuted and fail the floor without a code
change (#513).
Coverage says which lines ran, not whether a test would catch a wrong result. It does not replace the conformance assertions.