Swift Code Review
Review Workflow
Follow this sequence in order. Do not emit findings until every Pass below is satisfied.
-
Swift / toolchain baseline — Establish language and tooling context:
Package.swift// swift-tools-versionand any per-target Swift language version orswiftSettingsin the manifest; for Xcode,SWIFT_VERSION(or equivalent) in project or target build settings; note if review is single-file only.
Pass: You state a concrete Swift language version or mode (e.g. Swift 6 language mode, tools 5.10) before advice that depends on strict concurrency, migration-only syntax, or SDK availability. -
Read surrounding code — For each changed
.swiftfile, read the full enclosing type, function, method, or property that contains the edits, not only the diff hunk.
Pass: At least one full enclosing symbol (type or member) containing the change was read per changed file. -
Scope the checklist — Using Quick Reference, decide which Review Checklist rows and references apply; open those reference files; skip rows clearly unrelated to the diff.
Pass: The review (or working notes) lists which checklist areas you applied, or marks areas N/A with a one-line reason tied to the diff (e.g. “no SwiftUI / @Observable in change”). -
Pre-report verification — Load and follow review-verification-protocol.
Pass: That skill’s Hard gates (sequenced) are satisfied for each finding you will report (full symbol read, usage search before “unused”, caller checked before “missing handling”, severity calibrated,[FILE:LINE]proof).
Hard gates (same sequence, shorter)
| Step | Objective pass condition |
|---|---|
| 1 | Swift version/mode (or explicit single-file limitation) recorded before version- or SDK-gated advice. |
| 2 | Full enclosing symbol read per changed file, not diff-only. |
| 3 | Checklist areas + references listed or N/A with diff-tied reason. |
| 4 | review-verification-protocol completed for every reported issue. |
Output format
Report findings as:
[FILE:LINE] ISSUE_TITLE
Severity: Critical | Major | Minor | Informational
Description of the issue and why it matters.
Quick Reference
| Issue Type | Reference |
|---|---|
| async/await, actors, Sendable, Task | references/concurrency.md |
| @Observable, @ObservationIgnored, @Bindable | references/observable.md |
| throws, Result, try?, typed throws | references/error-handling.md |
| Force unwraps, retain cycles, naming | references/common-mistakes.md |
Review Checklist
- No force unwraps (
!) on runtime data (network, user input, files) - Closures stored as properties use
[weak self] - Delegate properties are
weak - Independent async operations use
async letorTaskGroup - Long-running Tasks check
Task.isCancelled - Actors have mutable state to protect (no stateless actors)
- Sendable types are truly thread-safe (beware
@unchecked) - Errors handled explicitly (no empty catch blocks)
- Custom errors conform to
LocalizedErrorwith descriptive messages - Nested @Observable objects are also marked @Observable
- @Bindable used for two-way bindings to Observable objects
When to Load References
- Reviewing async/await, actors, or TaskGroups → concurrency.md
- Reviewing @Observable or SwiftUI state → observable.md
- Reviewing error handling or throws → error-handling.md
- General Swift review → common-mistakes.md
Review Questions
- Are async operations that could run concurrently using
async let? - Could actor state change across suspension points (reentrancy bug)?
- Is
@unchecked Sendablebacked by actual synchronization? - Are errors logged and presented with helpful context?
- Could any closure or delegate create a retain cycle?