7.1 KiB
7.1 KiB
id, title, category, status, source_trust_level, verification_status, created_at, updated_at, tags, tech_stack, applied_in, aliases
| id | title | category | status | source_trust_level | verification_status | created_at | updated_at | tags | tech_stack | applied_in | aliases | |||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| ios-strict-concurrency | Swift 6 Strict Concurrency — Sendable / Isolation | Coding | draft | B | conceptual | 2026-05-09 | 2026-05-09 |
|
|
|
Swift 6 Strict Concurrency
Swift 6 = data race 컴파일 시 잡음.
Sendable강제, isolation 명시. 점진 도입 (@preconcurrency). 큰 코드는 마이그레이션 작업.
📖 핵심 개념
- Strict concurrency: 모든 cross-actor 가 검증.
- Sendable: thread-safe 표시.
- Isolation: 어떤 actor / context.
- @preconcurrency: 옛 코드 호환 가뭄.
💻 코드 패턴
활성화
// Package.swift
.target(
name: "MyApp",
swiftSettings: [
.enableExperimentalFeature("StrictConcurrency"),
]
)
// 또는 Xcode → Build Settings → Strict Concurrency Checking → Complete
// Swift 6 mode
swiftLanguageVersions: [.v6]
Sendable 자동
// 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
@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
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 명시
@MainActor
func updateUI() { ... }
@globalActor actor DatabaseActor {
static let shared = DatabaseActor()
}
@DatabaseActor
func saveToDB() { ... }
nonisolated
actor Logger {
private var logs: [String] = []
func add(_ msg: String) { logs.append(msg) }
nonisolated var name: String { "AppLogger" } // 동기 read OK
}
Cross-actor 호출
@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)
// Sendable X 인 객체도 일회성 전달 OK
actor Worker {
func process(_ data: sending NonSendable) {
// data 는 caller 가 더 사용 X (move-like)
}
}
→ Class 가 mutable 도 한 번만 보내면 OK.
@preconcurrency (legacy)
// 옛 lib import
import @preconcurrency Foundation
// 또는 specific
@preconcurrency import OldLib
→ Sendable warning 무시 (옛 lib 가 안 표시).
Async let + Sendable
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
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
class Base: Sendable {
let name: String
init(name: String) { self.name = name }
}
final class Sub: Base, @unchecked Sendable { // 명시 필요
// ...
}
// final 안 + Sendable = 어려움 — final 권장.
@unchecked Sendable (lock 직접 관리)
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 점진
// Phase 1: Strict concurrency = Minimal (default)
// Phase 2: Targeted (특정 module)
// Phase 3: Complete
// Module 별
.target(
name: "Core",
swiftSettings: [.enableExperimentalFeature("StrictConcurrency")]
)
Common errors + fix
// 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) { ... }
}
// 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
@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 마지막 카드.