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