--- id: ios-strict-concurrency title: Swift 6 Strict Concurrency — Sendable / Isolation category: Coding status: draft source_trust_level: B verification_status: conceptual created_at: 2026-05-09 updated_at: 2026-05-09 tags: [ios, swift, concurrency, vibe-coding] tech_stack: { language: "Swift 6", applicable_to: ["iOS"] } applied_in: [] aliases: [Swift 6, strict concurrency, Sendable, isolation, data race, complete checking] --- # Swift 6 Strict Concurrency > Swift 6 = data race 컴파일 시 잡음. **`Sendable` 강제, isolation 명시**. 점진 도입 (`@preconcurrency`). 큰 코드는 마이그레이션 작업. ## 📖 핵심 개념 - Strict concurrency: 모든 cross-actor 가 검증. - Sendable: thread-safe 표시. - Isolation: 어떤 actor / context. - @preconcurrency: 옛 코드 호환 가뭄. ## 💻 코드 패턴 ### 활성화 ```swift // Package.swift .target( name: "MyApp", swiftSettings: [ .enableExperimentalFeature("StrictConcurrency"), ] ) // 또는 Xcode → Build Settings → Strict Concurrency Checking → Complete ``` ```swift // Swift 6 mode swiftLanguageVersions: [.v6] ``` ### Sendable 자동 ```swift // Struct 가 모든 field Sendable 면 자동 Sendable struct User: Sendable { let id: UUID let email: String } // Class — 명시 필요 final class Config: Sendable { let theme: String init(theme: String) { self.theme = theme } } // Mutable class = Sendable X (자동) class MutableState { // ❌ 다른 actor 로 못 보냄 var x = 0 } ``` ### @MainActor ```swift @MainActor class ViewModel: ObservableObject { @Published var users: [User] = [] func load() async { let fetched = await api.fetchUsers() // off main users = fetched // back to main } } ``` → 자동 main thread. ### Actor isolation ```swift actor BankAccount { private var balance: Decimal = 0 func deposit(_ amount: Decimal) { balance += amount } var current: Decimal { balance } } let acc = BankAccount() await acc.deposit(100) // await 필요 (cross-actor) let bal = await acc.current ``` ### Isolation 명시 ```swift @MainActor func updateUI() { ... } @globalActor actor DatabaseActor { static let shared = DatabaseActor() } @DatabaseActor func saveToDB() { ... } ``` ### nonisolated ```swift actor Logger { private var logs: [String] = [] func add(_ msg: String) { logs.append(msg) } nonisolated var name: String { "AppLogger" } // 동기 read OK } ``` ### Cross-actor 호출 ```swift @MainActor func ui() {} actor DB { func write() {} } let db = DB() @MainActor func handle() async { await db.write() // cross-actor — await + Sendable args } ``` ### Sending parameters (Swift 6 의 새 feature) ```swift // Sendable X 인 객체도 일회성 전달 OK actor Worker { func process(_ data: sending NonSendable) { // data 는 caller 가 더 사용 X (move-like) } } ``` → Class 가 mutable 도 한 번만 보내면 OK. ### @preconcurrency (legacy) ```swift // 옛 lib import import @preconcurrency Foundation // 또는 specific @preconcurrency import OldLib ``` → Sendable warning 무시 (옛 lib 가 안 표시). ### Async let + Sendable ```swift async let user = api.fetchUser(id) // Sendable result async let orders = api.fetchOrders(uid) let dashboard = try await Dashboard(user: user, orders: orders) ``` ### TaskGroup — Sendable result ```swift func loadAll() async throws -> [URL: Data] { try await withThrowingTaskGroup(of: (URL, Data).self) { group in for url in urls { group.addTask { let (data, _) = try await URLSession.shared.data(from: url) return (url, data) // tuple 가 Sendable } } var result: [URL: Data] = [:] for try await (url, data) in group { result[url] = data } return result } } ``` ### Inheritance 와 Sendable ```swift class Base: Sendable { let name: String init(name: String) { self.name = name } } final class Sub: Base, @unchecked Sendable { // 명시 필요 // ... } // final 안 + Sendable = 어려움 — final 권장. ``` ### @unchecked Sendable (lock 직접 관리) ```swift final class ThreadSafeCache: @unchecked Sendable { private let lock = NSLock() private var storage: [String: String] = [:] func set(_ key: String, _ value: String) { lock.lock(); defer { lock.unlock() } storage[key] = value } } ``` → 검사 우회 — 책임 너에게. ### Migration 점진 ```swift // Phase 1: Strict concurrency = Minimal (default) // Phase 2: Targeted (특정 module) // Phase 3: Complete // Module 별 .target( name: "Core", swiftSettings: [.enableExperimentalFeature("StrictConcurrency")] ) ``` ### Common errors + fix ```swift // Error: "Sending non-Sendable value" class MyData { var x = 0 } Task { await actor.use(MyData()) } // ❌ // Fix 1: struct struct MyData: Sendable { var x = 0 } // Fix 2: final class + Sendable final class MyData: Sendable { let x: Int init(x: Int) { self.x = x } } // Fix 3: sending (Swift 6) actor MyActor { func use(_ data: sending MyData) { ... } } ``` ```swift // Error: "Cannot use mutable state from another isolation" @MainActor var counter = 0 Task { counter += 1 // ❌ — main actor 외 } // Fix Task { @MainActor in counter += 1 } // 또는 await MainActor.run { counter += 1 } ``` ### Async sequence isolation ```swift @MainActor class Stream { func observe() async { for await value in someAsyncStream { // 다른 actor 가 yield // value 가 Sendable 인지 검증 } } } ``` ### Performance impact ``` 거의 없음 — 컴파일 시 검사. 일부 boxing 추가 가능 (cross-actor sending). ``` ### Tooling ``` Xcode: warning / error 표시. swift -strict-concurrency=complete: CLI. ``` ### Adoption advice ``` 1. 새 프로젝트 = Swift 6 mode + complete. 2. 기존 = minimal → targeted → complete 점진. 3. 한 module 씩 + @preconcurrency 로 의존. 4. Sendable struct 우선 (class 보다). 5. @MainActor / actor isolation 명시. ``` ## 🤔 의사결정 기준 | 상황 | 추천 | |---|---| | 새 프로젝트 | Swift 6 + Complete | | 기존 큰 codebase | 점진 (minimal → complete) | | Old library 의존 | @preconcurrency | | UI / ViewModel | @MainActor | | Mutable shared state | actor | | Pure data | struct + Sendable | ## ❌ 안티패턴 - **@unchecked Sendable 남발**: data race 검사 무효. - **모든 거 @MainActor**: contention. - **Class 가 모든 곳 — Sendable 어려움**: struct 우선. - **Cross-actor 무거운 데이터 매번**: copy 비용. id 만 전달. - **nonisolated + mutable**: race. - **@preconcurrency 영원 유지**: 점진 마이그. - **`Task.detached` 무절제**: priority / context 잃음. ## 🤖 LLM 활용 힌트 - Swift 6 = Strict concurrency default. - Sendable struct + actor + @MainActor 3종. - @preconcurrency 점진 마이그. - @unchecked 마지막 카드. ## 🔗 관련 문서 - [[iOS_Swift_Concurrency_Actor_Patterns]] - [[iOS_Swift_Concurrency_async_await]] - [[iOS_Swift_Macros]]