Stack conventions
Swift
Swift 5.9/6 apps and packages (SwiftUI, Swift concurrency) built with Swift Package Manager or Xcode, tested with Swift Testing or XCTest.
Swift 5.9/6 apps and packages (SwiftUI, Swift concurrency) built with Swift Package Manager or Xcode, tested with Swift Testing or XCTest.
Used for: swift.
Principles
- Follow the Swift API Design Guidelines - clarity at the point of use, fluent call sites that read as English phrases, no needless words.
- Prefer value types (struct, enum) for data; use classes only for identity, shared mutable state or framework requirements, and mark them final by default.
- Use async/await and structured concurrency (async let, task groups) instead of completion handlers and ad-hoc dispatch queues.
- Isolate shared mutable state in actors or on @MainActor; make types crossing concurrency domains Sendable and keep strict concurrency checking on (Swift 6 language mode).
- Model failure with throws (typed throws where the error set is closed) and Result only at callback boundaries; never force-unwrap or force-try in production code.
- Make illegal states unrepresentable with enums with associated values and exhaustive switch statements.
- Keep access control minimal (private/fileprivate/internal by default; public only for the package API).
- Prefer protocols with small requirements and generics (some/any used deliberately) over class hierarchies.
- In SwiftUI, have exactly one owner per piece of state and pass derived data or bindings down.
Conventions
Accessibility
- Give every interactive control and meaningful image an accessibility label; mark decorative images as hidden (Image(decorative:) or .accessibilityHidden).
- Support Dynamic Type (system text styles, no fixed font sizes) and test at the largest sizes.
- Respect Reduce Motion and Increase Contrast; never convey information by colour alone.
- Group related elements (.accessibilityElement(children: .combine)) and provide accessibility actions for custom gestures.
Concurrency
- Annotate UI-facing types with @MainActor; never update UI from background tasks.
- Use structured concurrency (async let, withTaskGroup) and respect cancellation (Task.checkCancellation, Task.isCancelled).
- Avoid unstructured Task {} and Task.detached unless the work must outlive the caller; store and cancel long-lived tasks.
- Do not use @unchecked Sendable or nonisolated(unsafe) without a comment proving thread safety.
- Do not mix DispatchQueue locking with actors for the same state.
Dependencies
- Use Swift Package Manager with version requirements (from: "1.2.0", up to next major); commit Package.resolved for apps.
- Prefer Apple and swift.org packages (swift-collections, swift-algorithms, swift-log) before third-party libraries.
Documentation
- Document public APIs with /// DocC comments including - Parameters, - Returns and - Throws.
- Write the summary as a fragment describing what the declaration does or is.
- Provide DocC articles for packages with a public API.
Errors
- No force unwrap (!), force try (try!) or force cast (as!) outside tests and proven invariants; use guard let / if let and throwing initialisers.
- Define domain errors as enums conforming to Error (and LocalizedError where shown to users).
- Propagate errors with throws; catch at a boundary that can recover or present them; do not swallow with try? unless absence is the intended result.
- Use preconditions (precondition, assertionFailure) for programmer errors, not for user input.
Logging
- Use os.Logger (Apple platforms) or swift-log (server, Linux) with subsystems and categories; no print in shipped code.
- Mark interpolated personal data private in os.Logger (privacy: .private is the default for dynamic strings); never log tokens.
Naming
- UpperCamelCase types and protocols; lowerCamelCase functions, properties, enum cases.
- Protocols describing what something is are nouns (Collection); capabilities end in -able, -ible or -ing (Equatable, ProgressReporting).
- Name methods by side effects - nouns for non-mutating results (distance(to:)), imperative verbs for mutating (sort() vs sorted()).
- Booleans read as assertions (isEmpty, hasChanges); argument labels make call sites read naturally and omit needless words.
- Acronyms are uniformly upper or lower case (urlString, userID).
Performance
- Prefer value types and copy-on-write collections; avoid unnecessary class allocations and existential (any) boxing in hot paths.
- Use lazy sequences for chained transformations over large collections.
- Keep SwiftUI body cheap and views small so updates are granular; use stable identifiers in ForEach.
- Profile with Instruments before optimising.
Security
- Store secrets in the Keychain, never in UserDefaults, plist files or source.
- Keep App Transport Security on; pin certificates only with a rotation plan.
- Validate and decode external data with Codable into typed models; reject unexpected values.
- Use CryptoKit for cryptography; never roll your own.
- Never build SQL, predicates or JavaScript for a web view from user input; use bound parameters and typed APIs (injection).
Structure
- Use Swift Package Manager targets to modularise; keep features in separate targets with explicit dependencies.
- One primary type per file, file named after it; group with // MARK: - comments.
- Keep view code, view models/models and services in separate files; avoid massive view controllers or views.
- Enable the Swift 6 language mode (swiftLanguageModes in Package.swift / SWIFT_VERSION 6) for new targets.
Testing
- Swift Testing (@Test, #expect, #require, parameterised @Test(arguments:)) for new tests on Swift 6 toolchains; XCTest where the repository still uses it or for UI tests.
- Test async code with async test functions; avoid sleeps and expectations with long timeouts.
- Inject dependencies so tests use fakes rather than network or disk.
- Use snapshot or UI tests only for critical flows; keep unit tests fast.
- Unit tests cover view models and services with fakes; integration tests exercise the real persistence and networking layers against local stand-ins (URLProtocol stubs).
- Name tests after the behaviour (
@Test("rejects an order without lines")ortest_reject_orderWithoutLines). - Done means tested: new or changed behaviour ships with tests in the same change, and a fixed bug gets a regression test that fails without the fix.
Review checklist
- Do new APIs follow the Swift API Design Guidelines (labels, naming, no needless words)?
- Are there no force unwraps, force tries or force casts outside tests?
- Are data types value types where possible, and classes final?
- Does the code compile cleanly under strict concurrency (Swift 6 mode) without @unchecked Sendable shortcuts?
- Is UI state updated on the main actor and owned by a single source of truth?
- Are async tasks structured and cancellation honoured?
- Are errors modelled with throws and specific error types, not swallowed with try?
- Are secrets stored in the Keychain and personal data kept out of logs?
- Do interactive views have accessibility labels and support Dynamic Type?
- Are there tests (Swift Testing or XCTest) for new behaviour?
- Is access control as narrow as possible?
- Does SwiftLint/swift-format report no new violations?
Quality checks (run when your repository has the tool configured)
| Check | Kind | Tool | Blocks the pull request |
|---|---|---|---|
swiftlint |
lint | swiftlint | no |
swift-format |
format | swift-format | no |
osv-scanner |
security | osv-scanner | no |
gitleaks |
security | gitleaks | no |