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

3.8 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-swift-concurrency-async-await Swift Concurrency — async/await + actors Coding draft B conceptual 2026-05-09 2026-05-09
ios
swift
concurrency
async-await
actor
vibe-coding
language applicable_to
Swift 5.5+
iOS
macOS
watchOS
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

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

@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

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

let task = Task {
    for i in 0..<1000 {
        try Task.checkCancellation() // cancel 시 throw
        await process(i)
    }
}
// 나중에
task.cancel()

Actor — 격리된 상태

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 는 거의 안 씀.

🔗 관련 문서