---
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]]
