--- id: ios-swift-concurrency-async-await title: Swift Concurrency — async/await + actors 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, async-await, actor, vibe-coding] tech_stack: { language: "Swift 5.5+", applicable_to: ["iOS", "macOS", "watchOS"] } applied_in: [] aliases: [structured concurrency, Task, MainActor, actor isolation] --- # Swift Concurrency > Swift 의 async/await + Task + Actor 는 **structured concurrency**. callback hell 끝. 핵심: (1) Task 트리는 부모 cancel = 자식 cancel, (2) UI 업데이트는 `@MainActor`, (3) 공유 mutable 상태는 actor. ## 📖 핵심 개념 - `async` 함수: suspension 가능 함수. - `await`: suspend 지점. - `Task`: 동시성 단위. 부모-자식 구조. - `actor`: 격리된 가변 상태. 한 시점 한 메서드만 실행. - `@MainActor`: UI 스레드에서 실행 보장. ## 💻 코드 패턴 ### 기본 async fetch ```swift func loadUser(id: String) async throws -> User { let (data, _) = try await URLSession.shared.data(from: URL(string: "https://api.example.com/users/\(id)")!) return try JSONDecoder().decode(User.self, from: data) } Task { do { let user = try await loadUser(id: "1") await MainActor.run { self.user = user } } catch { print("failed: \(error)") } } ``` ### MainActor + ViewModel ```swift @MainActor final class UserViewModel: ObservableObject { @Published var user: User? @Published var isLoading = false func load(id: String) async { isLoading = true defer { isLoading = false } do { user = try await loadUser(id: id) } catch { /* surface error */ } } } ``` ### 동시 실행 — async let ```swift func loadProfile(id: String) async throws -> Profile { async let user = loadUser(id: id) async let posts = loadPosts(id: id) return Profile(user: try await user, posts: try await posts) } ``` ### Task cancellation ```swift let task = Task { for i in 0..<1000 { try Task.checkCancellation() // cancel 시 throw await process(i) } } // 나중에 task.cancel() ``` ### Actor — 격리된 상태 ```swift actor Counter { private var value = 0 func increment() { value += 1 } func current() -> Int { value } } let c = Counter() await c.increment() let v = await c.current() ``` ## 🤔 의사결정 기준 | 작업 | 도구 | |---|---| | 단순 비동기 호출 | async/await | | 두 비동기 동시 + 모두 기다림 | async let | | 여러 동시 + 결과 collect | TaskGroup | | UI 업데이트 | @MainActor | | 공유 가변 상태 | actor | | Timer / Combine 호환 | AsyncStream | | Cancel 가능 작업 | Task + checkCancellation | ## ❌ 안티패턴 - **Task { } 안에서 self 강한 참조**: 메모리 leak. `[weak self]` 사용. - **DispatchQueue 와 async/await 혼용**: 콜백 → continuation → 옛 패턴. withCheckedContinuation 으로 통합. - **MainActor 메서드를 background 에서 직접 호출**: 컴파일 에러 또는 런타임 violation. await + MainActor 명시. - **actor 메서드 안에서 long-running 동기 작업**: 다른 호출 차단. async 로 분할. - **Task 안에서 try? 로 에러 silently 삼킴**: 디버깅 지옥. 최소 logging. - **structured concurrency 깨기 (`Task.detached`)**: 부모-자식 구조 끊김. 정말 독립 작업일 때만. - **for await loop 가 너무 길어서 cancel 무시**: checkCancellation 정기 호출. ## 🤖 LLM 활용 힌트 - "@MainActor 로 ViewModel 마크. UI 업데이트는 자동 main thread" 명시. - 공유 상태는 actor, UI 는 MainActor, 백그라운드 작업은 일반 async. - Task.detached 는 거의 안 씀. ## 🔗 관련 문서 - [[iOS_Swift_Result_Type]] - [[iOS_Swift_Memory_ARC_Cycles]]