shredbx logo
shredbx shredbx shredbx shredbx Personal
  • Home
  • Lab
  • Portfolio
  • Experience
  • Services
  • Profile
  • Contact
AClaude
  • Home
  • Lab
  • Portfolio
  • Experience
  • Services
  • Profile
  • Contact
Andrei Solovev
Knowledge
Search knowledge... ⌘K
Knowledge · Guidelines · swift

Swift Actor Sendable Boundary

Cross the actor → @MainActor boundary safely in Swift 6 strict concurrency. Keep view models as plain Sendable data, never pass non-Sendable types (NSAttributedString, NSView, NSButton, UIView) across isolation domains, and avoid holding @MainActor references from within actor-isolated code.

Andrei Solovev

Metadata

swift swift mandatory

Procedures

Showing 3 of 5

  1. 1 Make every ViewModel a Sendable struct of plain data
    ```swift
    struct RecordingViewModel: Equatable, Sendable {
        let statusLabel: String
        let stableText: String        // plain String — not NSAttributedString
        let tentativeText: String     // plain String
        let recordButtonEnabled: Bool
        let isRecording: Bool
        // ... all fields are Sendable value types or protocol-bound Sendable types
    }
    ```
    The view layer builds the NSAttributedString LOCALLY from
    `(stableText, tentativeText, themeTokens)` in the view's
    render method. Non-Sendable types never cross the boundary.
  2. 2 Hop from actor to @MainActor via `Task { @MainActor in ... }` or `await MainActor.run { ... }`
    ```swift
    actor TranscriptionService {
        func handleStreamUpdate(stable: String, tentative: String) {
            // ... actor work ...
            let snapshotStable = stable  // captured by value
            let snapshotTentative = tentative
            Task { @MainActor in
                AppContainer.current.resolve(RecordingStateMachine.self)
                    .handle(.streamUpdate(stable: snapshotStable,
                                          tentative: snapshotTentative))
            }
        }
    }
    ```
  3. 3 Never store a @MainActor-isolated class inside an actor
    BAD:
    ```swift
    actor TranscriptionService {
        let machine: RecordingStateMachine  // ERROR: @MainActor type in actor
    }
    ```
    GOOD — the actor holds a Sendable closure provided by the
    composition root. The closure internally hops to MainActor:
    ```swift
    actor TranscriptionService {
        let dispatch: @Sendable (RecordingEvent) -> Void
        init(dispatch: @escaping @Sendable (RecordingEvent) -> Void) {
            self.dispatch = dispatch
        }
        func handleStreamUpdate(...) { dispatch(.streamUpdate(...)) }
    }
    
    // In AppCoordinator (composition root):
    let machine = RecordingStateMachine(...)
    let service = TranscriptionService { event in
        Task { @MainActor in machine.handle(event) }
    }
    ```
shredbx logo shredbx shredbx shredbx shredbx Andrei Solovev

Solution Architect & Lead Software Engineer

ExperiencePortfolioResearch & ExperimentsEducationCertificationSkills
GitHub ↗LinkedIn ↗Email ↗
AVAILABLE FOR NEW PROJECTS
// MY LATEST BEATS
Hobby & Interests

Lab

  • The Lab
  • Framework
  • Components
  • Packages
  • Games
  • Process (SDLC)
  • Knowledge
  • Blog

Andrei

  • Portfolio
  • Experience
  • Services
  • Profile
  • Contact
  • Lifestyle

Team

  • Team
  • Andrei
  • Claude

Legal

  • Privacy
  • Terms
  • Cookies
© 2026 shredbx.com. All rights reserved. — Andrei Solovev |