fix(axiom-concurrency): stop flagging UIKit view controllers as missing @MainActor

concurrency-auditor Pattern 1 reported every UIViewController and UIView
subclass without @MainActor as CRITICAL. Both classes are NS_SWIFT_UI_ACTOR,
so their subclasses already inherit main-actor isolation. Scope the pattern to
ObservableObject and other UI-state classes, skip it when the target's
SWIFT_DEFAULT_ACTOR_ISOLATION is MainActor, and list UIKit subclasses as a
false positive. The audit command page's example output now matches.

Verified: an unannotated UIViewController/UIView subclass called from a
nonisolated function fails to compile against the iOS 27.1 SDK; a plain
ObservableObject does not.
This commit is contained in:
Charles Wiltgen
2026-09-19 10:52:36 -07:00
parent 91a7b9a22e
commit 6c09a1814c
3 changed files with 10 additions and 8 deletions
@@ -89,11 +89,11 @@ Patterns 1, 2, 3, 6, and 7 are unaffected — the compiler has nothing to say ab
### 1. Missing @MainActor on UI Classes (CRITICAL/HIGH)
**Pattern**: UIViewController, UIView, ObservableObject without @MainActor
**Search**: `class.*UIViewController`, `class.*ObservableObject` — check 5 lines before for @MainActor
**Pattern**: ObservableObject (and other UI-state classes) without @MainActor
**Search**: `class.*ObservableObject` — check 5 lines before for @MainActor
**Issue**: Crashes when UI modified from background threads
**Fix**: Add `@MainActor` to class declaration
**Note**: SwiftUI Views are implicitly @MainActor — not an issue
**Note**: `UIViewController` and `UIView` subclasses are NOT findings — both classes are `NS_SWIFT_UI_ACTOR`, so every subclass inherits `@MainActor` without an annotation. SwiftUI Views are implicitly @MainActor too. Skip this pattern entirely when the target sets `SWIFT_DEFAULT_ACTOR_ISOLATION = MainActor`: every unannotated class is already @MainActor there.
**Field signal**: Crashes with xcsym `pattern_tag=swift_concurrency_violation` (fires on `_swift_task_isCurrentExecutor` in the exception subtype) almost always trace back to this anti-pattern. If the user has `.ips` artifacts, run `xcsym crash --format=summary <file>` and correlate the crashed frames with grep hits.
### 2. Unsafe Task Self Capture (HIGH/HIGH)
@@ -252,6 +252,7 @@ If >100 total issues: Summarize by category, show only CRITICAL/HIGH details
- Async functions with minimal computation (a single network call, a short string format) — don't flag for missing @concurrent
- @MainActor classes accessing their own properties
- SwiftUI Views (implicitly @MainActor)
- `UIViewController` / `UIView` subclasses without `@MainActor` (inherited from UIKit's `NS_SWIFT_UI_ACTOR`)
- Task captures where self is a struct (value type)
- `@unchecked Sendable` with clear migration comment (downgrade to LOW)
- GCD usage in legacy modules marked for future migration
@@ -80,11 +80,11 @@ Patterns 1, 2, 3, 6, and 7 are unaffected — the compiler has nothing to say ab
### 1. Missing @MainActor on UI Classes (CRITICAL/HIGH)
**Pattern**: UIViewController, UIView, ObservableObject without @MainActor
**Search**: `class.*UIViewController`, `class.*ObservableObject` — check 5 lines before for @MainActor
**Pattern**: ObservableObject (and other UI-state classes) without @MainActor
**Search**: `class.*ObservableObject` — check 5 lines before for @MainActor
**Issue**: Crashes when UI modified from background threads
**Fix**: Add `@MainActor` to class declaration
**Note**: SwiftUI Views are implicitly @MainActor — not an issue
**Note**: `UIViewController` and `UIView` subclasses are NOT findings — both classes are `NS_SWIFT_UI_ACTOR`, so every subclass inherits `@MainActor` without an annotation. SwiftUI Views are implicitly @MainActor too. Skip this pattern entirely when the target sets `SWIFT_DEFAULT_ACTOR_ISOLATION = MainActor`: every unannotated class is already @MainActor there.
**Field signal**: Crashes with xcsym `pattern_tag=swift_concurrency_violation` (fires on `_swift_task_isCurrentExecutor` in the exception subtype) almost always trace back to this anti-pattern. If the user has `.ips` artifacts, run `xcsym crash --format=summary <file>` and correlate the crashed frames with grep hits.
### 2. Unsafe Task Self Capture (HIGH/HIGH)
@@ -243,6 +243,7 @@ If >100 total issues: Summarize by category, show only CRITICAL/HIGH details
- Async functions with minimal computation (a single network call, a short string format) — don't flag for missing @concurrent
- @MainActor classes accessing their own properties
- SwiftUI Views (implicitly @MainActor)
- `UIViewController` / `UIView` subclasses without `@MainActor` (inherited from UIKit's `NS_SWIFT_UI_ACTOR`)
- Task captures where self is a struct (value type)
- `@unchecked Sendable` with clear migration comment (downgrade to LOW)
- GCD usage in legacy modules marked for future migration
@@ -10,7 +10,7 @@ Scan your Swift codebase for common Swift 6 concurrency anti-patterns and violat
## What This Command Checks
1. **Missing @MainActor on UI Classes** View controllers and ObservableObjects without @MainActor
1. **Missing @MainActor on UI Classes** ObservableObjects and other UI-state classes without @MainActor (view controllers and views inherit it from UIKit)
2. **Unsafe Task Self Capture** Tasks capturing self strongly without [weak self]
3. **Sendable Violations** Non-Sendable types crossing actor boundaries
4. **Improper Actor Isolation** Unsafe data access from actor contexts
@@ -124,7 +124,7 @@ The command will:
```
🔴 CRITICAL: Missing @MainActor (4 issues)
- ProfileViewModel.swift:12 - ObservableObject without @MainActor
- SettingsVC.swift:23 - UIViewController without @MainActor
- SettingsStore.swift:23 - ObservableObject without @MainActor
Impact: Potential data race crashes
🔴 CRITICAL: Unsafe Task Self Capture (2 issues)