---
title: maniramezan/swiftcommons
framework: Swift Package Catalog
role: article
path: packages/maniramezan/swiftcommons
---

# maniramezan/swiftcommons

Shared Swift utilities and helpers for reusable app code.

## Requirements

- Swift 6.2 toolchain - macOS 15+, iOS 18+, Mac Catalyst 18+ (the package's declared minimums; they apply whether or   not the `SwiftData` trait is enabled)

## Installation

Add SwiftCommons as a Swift Package Manager dependency:

```swift dependencies: [     .package(url: "https://github.com/maniramezan/SwiftCommons.git", from: "0.12.0") ] ```

SwiftCommons ships three products:

- **`SwiftCommons`** — the main library. Add this to any target that uses the APIs below. - **`SwiftCommonsTestSupport`** — test-only helpers (a TestCommons-backed fake clock, `LoadingState` assertions,   in-memory SwiftData context, sync test fixtures). Add this to your test targets only. - **`SwiftCommonsSpeech`** — on-device speech-to-text, text-to-speech, and read-aloud transcript   alignment (see [Speech](#speech)). A separate product so apps that don't use speech don't link   `Speech` or `AVFAudio`. Live recognition needs iOS 26 / macOS 26.

```swift .target(name: "MyApp", dependencies: [     .product(name: "SwiftCommons", package: "SwiftCommons") ]), .testTarget(name: "MyAppTests", dependencies: [     "MyApp",     .product(name: "SwiftCommons", package: "SwiftCommons"),     .product(name: "SwiftCommonsTestSupport", package: "SwiftCommons"), ]), ```

### Package traits

| Trait | Default | Enables | |---|---|---| | `SwiftData` | on | `ModelContainer.make(for:inMemory:)`, `SyncEngine`, `SyncResourceAdapter`, `SyncMetadata`, and the SwiftData test helpers (`makeInMemoryModelContext`, `makeInMemorySyncContainer`) | | `CSV` | off | `CSV.parseRows` / `CSV.serializeRows` |

With no `traits:` argument you get the defaults, so the `SwiftData` APIs are included. A package that doesn't use SwiftData (for example, a networking library) can leave it out and keep everything else, including the sync DTOs, `SyncableModel`, `SyncState`, and `SyncEvent`:

```swift .package(url: "https://github.com/maniramezan/SwiftCommons.git", from: "0.12.0", traits: []) ```

Listing traits replaces the defaults instead of adding to them. To add CSV and keep SwiftData, include `.defaults`:

```swift .package(     url: "https://github.com/maniramezan/SwiftCommons.git", from: "0.12.0",     traits: [.defaults, "CSV"]) ```

## Features

- **Extensions** — safe/defaulted `Array` subscripts, `Optional.ifNil(_:)`, `String` blank/trim   helpers, the `StringParsable` protocol (`T(parsing:)` with a lenient `Bool` parser) and   `String(flag:)` boolean-flag helpers, `Bundle` app-version accessors, `UUID` byte/deterministic   helpers, `Duration.timeInterval`, `URL` string-literal support, and locale-aware   `NumberFormatter`/`FixedWidthInteger` helpers. - **Decoding diagnostics** — `DecodingError.debugSummary` (e.g.   `keyNotFound 'id' at items[2].user`) for logging decode failures without payload values. - **Dates & calendars** — `Calendar` date math (start of week/month, inclusive date ranges, month   arithmetic), a thread-safe `DateFormatter` cache, and `Date.relativeDescription(...)` for   human-readable relative times ("1 hour ago"). - **Localization** — `Language`/`Country` enums and `Locale` identifier helpers. - **Logging** — OSLog `Logger` conveniences for categories, privacy, and context. - **Library logging** — `LibraryLogger`, a level-gated OSLog front end whose verbosity a library's   users control (`setLevel(_:)`), with per-category handles and private URL/error logging. - **Formatting** — `DurationFormatter` for compact `m:ss` / `h:mm:ss` durations. - **Errors** — `AnySendableError`, a `Hashable` snapshot of any error (type, domain, code,   descriptions) for storing or comparing errors as values. - **State** — a generic `LoadingState` machine (`idle`/`loading`/`loaded`/`failed`) with a   user-safe `LoadingError`, including a `LoadingState.load { ... }` helper for wrapping async work.   `LoadingErrorKind` and `LoadingErrorClassifiable` distinguish offline, timeout, and other failures   without displaying internal error details; apps can map `LoadingError.kind` to localized copy. - **App versions** — `AppVersion(string:)` parses and compares numeric marketing versions,   normalizing missing components to zero and ignoring prerelease/build suffixes. - **Month grids** — `CalendarArithmetic.gridDates(in:)` returns day starts padded to full weeks,   respecting the calendar's first weekday, time zone, leap months, and era segments. - **Configuration** — `ConfigValue`, a lenient `bool`/`string`/`int`/`double` coercion type, with   loaders for `ProcessInfo.environment` and decoded property lists, plus a `ConfigValueType` tag and   `ConfigValue(string:valueType:)` for decoding a textual value whose type is known separately. - **Async fan-out** — `AsyncBroadcaster`, one source broadcast to many `AsyncStream`s, with   optional replay of the latest value. - **Concurrency** — `AsyncLock` (FIFO mutex), `AsyncSemaphore` (counting semaphore), `Debouncer`,   and `withRetry(...)` — all built on the injectable `DelayClock` abstraction so consumers   can substitute a fake clock in tests. - **Retry backoff** — `RetryBackoff` (`.constant`, `.exponential` with cap and jitter) and a   `shouldRetry` predicate for `withRetry(attempts:backoff:clock:shouldRetry:operation:)`. - **Timeouts** — `withTimeout(_:clock:operation:)` races async work against an injectable clock,   cancels the losing task, and throws `TimeoutError` when time expires. Operations must cooperate   with cancellation for prompt completion. - **CSV** — lightweight CSV parsing/serialization (behind the `CSV` package trait). - **Call coalescing** — `SingleFlight<Key, Value>`, which shares one in-flight async call among   concurrent callers for the same key. - **Persistence** — `ModelContainer.make(for:inMemory:)`, a thin SwiftData bootstrap helper for   apps, previews, and tests. - **Sync** — a generic `SyncEngine` that drives offline, cross-device sync for SwiftData-backed   resources through one contract-owning loop; resources plug in as `SyncResourceAdapter` values.

## Usage

### Collections & optionals

```swift import SwiftCommons

let value = [1, 2, 3][safe: 10]          // nil — no crash let fallback = ["a", "b"][5, default: "n/a"]  // "n/a" let slice = [1, 2, 3][safe: 1..<10]      // [2, 3]

let name: String? = nil let resolved = name.ifNil("Anonymous")   // "Anonymous" ```

### Strings, Bundle, and UUID

```swift "  hi  ".trimmed        // "hi" "   ".nilIfBlank        // nil

Bool(parsing: "yes")   // true — also accepts 1/0, on/off, true/false Int(parsing: "42")     // 42 — T(parsing:) works for any StringParsable String(flag: true)     // "1"

Bundle.main.versionAndBuildNumber  // "2.3.1 (142)"

UUID().bytes             // [UInt8], canonical byte order ```

### Numbers & currency

```swift 1234.digits                                          // [4, 3, 2, 1] NumberFormatter.formatDecimal(1234.5, fractionDigits: 2...2) // "1,234.50" in en_US NumberFormatter.formatPercent(0.25)                    // "25%" in en_US NumberFormatter.formatYear(2024)                      // "2024" (digits follow the locale) NumberFormatter.formatCurrency(9.99, currencyCode: "USD") // "$9.99" ```

### Dates & calendars

```swift let formatter = DateFormatter.formatter(.MMMMddyyyy) // cached per thread let dateAndTime = DateFormatter.formatter(dateStyle: .medium, timeStyle: .short) let monthLabel = DateFormatter.formatter(template: "yMMMM", calendar: .current) // Cached formatters are shared on the calling thread: treat them as read-only // and use them synchronously without passing them between tasks or threads. let dateString = formatter.string(from: Date())

let calendar = Calendar(identifier: .gregorian) let monthStart = try calendar.startOfMonth(for: Date()) let days = try calendar.numberOfDays(in: 2, year: 2024) // 29

Date().addingTimeInterval(-3600).relativeDescription() // "1 hour ago" ```

### Localization

```swift let id = Locale.identifier(language: .french, country: .canada) // "fr-CA" let preset = Locale.Identifiers.englishUS                        // "en-US" let persian = Locale(identifier: "fa_IR").withNumberingSystemIdentifier(.arabExtended) ```

### Logging

```swift let logger = Logger.swiftCommonsLogger(for: MyType.self) logger.infoPublic("Started") logger.error("Failed to load", error: error, context: "id=\(id)") ```

### Signposts

`SignpostRecorder` wraps `OSSignposter` for measuring work that has to fit inside a frame — scrolling, animation, rendering. Recording is free when no profiler is attached.

```swift let signposts = SignpostRecorder(subsystem: "MyApp", category: "Rendering")

let grid = signposts.measure("resolveGrid") { expensiveGridResolution() }

// Start and end driven by separate callbacks let state = signposts.begin("scrollSettle") signposts.end("scrollSettle", state) ```

Record with Instruments' **os_signpost** instrument, or `xcrun xctrace record --template 'os_signpost' --attach <pid>`.

### State & configuration

```swift var state: LoadingState<[Item]> = await .load { try await api.fetchItems() } if let items = state.value { /* ... */ } if let error = state.error { showError(error.message, retryable: error.isRetryable) }

let config = ConfigValue.environment() let isDebug = config["DEBUG_MODE"]?.boolValue ?? false

// Decode a textual value when its type is known separately ConfigValue(string: "yes", valueType: .bool)  // .bool(true) ConfigValue(string: "7.5", valueType: .int)   // nil ```

### Concurrency

```swift let data = try await withRetry(attempts: 3, delay: .seconds(1)) {     try await fetchData() }

let debouncer = Debouncer(delay: .milliseconds(300)) func searchFieldDidChange(_ query: String) async {     await debouncer.run { await performSearch(query) } }

let lock = AsyncLock() // serialize work that spans awaits let result = try await lock.withLock { try await refreshToken() }

let semaphore = AsyncSemaphore(value: 3) // cap concurrent requests let response = try await semaphore.withPermit { try await urlSession.data(from: url) } ```

### Timeouts, versions, and month grids

```swift let items = try await withTimeout(.seconds(10)) {     try await fetchItems() }

let installedVersion = AppVersion(string: "2.4") let recommendedVersion = AppVersion(string: "2.5.0-preview")

let arithmetic = CalendarArithmetic(calendar: Calendar(identifier: .gregorian)) let dates = arithmetic.gridDates(in: MonthIdentifier(month: 2, year: 2028)) ```

`AppVersion` compares numeric app versions; it does not implement semantic-version prerelease precedence. `gridDates(in:)` returns `nil` for an invalid or mismatched month and includes padding dates outside the requested month. Use `arithmetic.month(containing:)` to identify that padding.

`withTimeout` waits for both child tasks to finish cleanup. An operation that ignores cancellation can keep it suspended beyond the timeout. Parent cancellation throws `CancellationError`.

`LoadingError(from:)` recognizes errors conforming to `LoadingErrorClassifiable`, native offline and timeout URL errors, and `TimeoutError`. Other errors retain the generic safe message. For localized presentation, map `LoadingError.kind` to app-owned strings; explicit messages remain available through `LoadingError(message:isRetryable:requiresSignIn:kind:)`.

### Speech

`SwiftCommonsSpeech` wraps Apple's on-device `SpeechAnalyzer` and `AVSpeechSynthesizer` behind small protocols (`SpeechRecognizing`, `SpeechSynthesizing`) so features can be tested with fakes.

```swift import SwiftCommonsSpeech

// 1. Permissions (Info.plist needs NSMicrophoneUsageDescription and //    NSSpeechRecognitionUsageDescription) and the locale's model. guard await SpeechRecognitionPermissions.requestAll() else { return } let locale = Locale(identifier: "en_US") if await SpeechModelAssets.status(for: locale) != .installed {     try await SpeechModelAssets.install(for: locale) { progress in print(progress) } }

// 2. Live transcript; on iOS, activate a .playAndRecord AVAudioSession first. let recognizer = SpeechRecognitionSession() let events = try await recognizer.start(     SpeechRecognitionConfiguration(locale: locale, autoStopAfterSilence: .seconds(1.5))) var transcript = SpeechTranscript() for try await event in events {     if case .transcript(let latest) = event { transcript = latest } }

// 3. Speak a reply with the best installed voice. let speech = SpeechSynthesisSession() for await _ in speech.speak(SpeechSynthesisRequest(text: "Nice work!", languageCode: "en-US")) {}

// 4. Grade a read-aloud attempt word by word. let alignment = TranscriptAlignment.align(     reference: "The quick brown fox", heard: transcript.words.map(\.transcriptToken)) alignment.tokens   // .match / .lowConfidence / .substitution / .omission / .insertion alignment.score    // 0...1 ```

Alignment compares what the recognizer *understood*: it catches missed, extra, and misheard words, but a recognizer often repairs mildly mispronounced words, so it isn't phoneme-level pronunciation scoring. Leave `contextualStrings` empty when grading, or the recognizer is biased toward the expected sentence.

### Sync (SwiftData)

`SyncEngine` drives offline sync for SwiftData rows that conform to `SyncableModel`. Each resource plugs in with one `SyncResourceAdapter` (a struct of closures); the engine owns the ack guard, pending guard, pagination, full-snapshot reconciliation, and full-resync recovery.

```swift @Model final class Item: SyncableModel {     var key: String     var title: String     var serverId: Int?     var updatedAt: Date     var isTombstoned: Bool      // never name this `isDeleted` on a @Model     var syncState: SyncState     var localUpdatedAt: Date     // init ... }

// SyncMetadata stores each resource's cursor — it must be in your schema. let container = try ModelContainer.make(for: Item.self, SyncMetadata.self) let engine = SyncEngine(modelContainer: container, events: { analytics.track($0) })

let items = SyncResourceAdapter<Item, ItemUpsert, ItemDelete, ItemChange>(     resourceName: "items",     fetchPending: { try $0.fetch(FetchDescriptor<Item>(predicate: #Predicate { $0.syncState != .synced })) },     businessKey: { $0.key },     makeUpserts: { $0.filter { !$0.isTombstoned }.map(ItemUpsert.init) },     makeDeletes: { $0.filter(\.isTombstoned).map(ItemDelete.init) },     call: { try await api.sync("items", $0) },     findExisting: { change, context in /* fetch by change.key */ },     changeKey: { $0.key },     isChangeDeleted: { $0.isDeleted },     upsertFromChange: { change, existing, context in /* apply or insert */ },     fetchActive: { try $0.fetch(FetchDescriptor<Item>(predicate: #Predicate { !$0.isTombstoned })) },     purgeSynced: { try $0.delete(model: Item.self, where: #Predicate { $0.syncState == .synced }) } )

try await engine.syncAll([AnySyncResource(items)]) ```

`SyncMetadata` uses `@Attribute(.unique)`, which CloudKit-backed containers don't support — keep it in a local (non-CloudKit) store. See the *Syncing SwiftData Resources* DocC article for the full contract.

### Persistence (SwiftData)

```swift let container = try ModelContainer.make(for: Item.self, Tag.self) let previewContainer = try ModelContainer.make(for: Item.self, inMemory: true) ```

### Testing your own code

Add the `SwiftCommonsTestSupport` product to your test target to get:

```swift import SwiftCommons import SwiftCommonsTestSupport import Testing

let clock = ManualClock() let debouncer = Debouncer(delay: .seconds(1), clock: clock) await debouncer.run { /* action under test */ } try #require(await clock.waitForSleepCount(1)) await clock.advance(by: .seconds(1)) // resumes the pending action deterministically

let context = try makeInMemoryModelContext(for: Item.self)

let state = await LoadingState.load { try await fetchItems() } expectLoaded(state) // records a test failure if `state` isn't `.loaded`

// Sync: a container that already includes SyncMetadata, plus DTO builders. let container = try makeInMemorySyncContainer(for: Item.self) let response = SyncResponseDTO<ItemChange>.fixture(     applied: [.fixture(key: "run", id: 101, status: "created")], cursor: "c1") ```

## Agent skills

The repo ships [Agent Skills](https://skills.sh) that teach coding agents (Claude Code, Codex, Cursor, …) to use SwiftCommons correctly in *your* app. Install them into a consuming project:

```bash npx skills add maniramezan/SwiftCommons                         # all three npx skills add maniramezan/SwiftCommons --skill swiftcommons-sync ```

- `swiftcommons` — "use this instead of hand-rolling it" API map, logging conventions, pitfalls. - `swiftcommons-sync` — adding a `SyncEngine` resource end to end. - `swiftcommons-testing` — `SwiftCommonsTestSupport` patterns (fake clock, fixtures).

## Documentation

Full API documentation is published with DocC to GitHub Pages.

Generate documentation locally:

```bash swift package generate-documentation \   --target SwiftCommons \   --output-path /tmp/docc ```

## Package Metadata

Repository: maniramezan/swiftcommons

Default branch: main

README: README.md
