Files
2nd/10_Wiki/Topics/Coding/iOS_Strict_Concurrency.md
T
2026-05-09 21:08:02 +09:00

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
ios
swift
concurrency
vibe-coding
language applicable_to
Swift 6
iOS
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: 옛 코드 호환 가뭄.

💻 코드 패턴

활성화

// 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 마지막 카드.

🔗 관련 문서