[2026] Swift 비동기 프로그래밍 | async/await, Task

[2026] Swift 비동기 프로그래밍 | async/await, Task

이 글의 핵심

Swift 비동기 프로그래밍: async/await, Task. async/await·Task.

들어가며

async/await는 콜백 깊이를 줄이고, 구조화된 동시성(태스크·액터)과 함께 쓰기 좋습니다. 취소·우선순위는 Task API로 전달합니다.

1. async/await

기본 사용

아래 코드는 swift를 사용한 구현 예제입니다. 함수를 통해 로직을 구현합니다, 비동기 처리를 통해 효율적으로 작업을 수행합니다, 에러 처리를 통해 안정성을 확보합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

func fetchData() async -> String {
    // 네트워크 요청 시뮬레이션
    try? await Task.sleep(nanoseconds: 1_000_000_000)
    return "데이터"
}
// 사용
Task {
    let data = await fetchData()
    print(data)
}

async throws

다음은 swift를 활용한 상세한 구현 코드입니다. 함수를 통해 로직을 구현합니다, 비동기 처리를 통해 효율적으로 작업을 수행합니다, 에러 처리를 통해 안정성을 확보합니다, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

enum NetworkError: Error {
    case badURL
    case timeout
}
func fetchData(from url: String) async throws -> String {
    guard !url.isEmpty else {
        throw NetworkError.badURL
    }
    
    try await Task.sleep(nanoseconds: 1_000_000_000)
    return "데이터"
}
// 사용
Task {
    do {
        let data = try await fetchData(from: "https://example.com")
        print(data)
    } catch {
        print("에러: \(error)")
    }
}

2. Task

Task 생성

다음은 swift를 활용한 상세한 구현 코드입니다. 비동기 처리를 통해 효율적으로 작업을 수행합니다, 에러 처리를 통해 안정성을 확보합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

// 기본 Task
let task = Task {
    let data = await fetchData()
    print(data)
}
// 우선순위 지정
let highPriorityTask = Task(priority: .high) {
    let data = await fetchData()
    print(data)
}
// 취소 가능한 Task
let cancellableTask = Task {
    for i in 1...10 {
        if Task.isCancelled {
            print("작업 취소됨")
            return
        }
        print(i)
        try? await Task.sleep(nanoseconds: 100_000_000)
    }
}
// 취소
cancellableTask.cancel()

async let - 병렬 실행

다음은 swift를 활용한 상세한 구현 코드입니다. 함수를 통해 로직을 구현합니다, 비동기 처리를 통해 효율적으로 작업을 수행합니다, 에러 처리를 통해 안정성을 확보합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

func fetchUser() async -> String {
    try? await Task.sleep(nanoseconds: 1_000_000_000)
    return "사용자"
}
func fetchPosts() async -> [String] {
    try? await Task.sleep(nanoseconds: 1_000_000_000)
    return ["포스트1", "포스트2"]
}
// 순차 실행 (느림)
Task {
    let user = await fetchUser()
    let posts = await fetchPosts()
    print(user, posts)
}
// 병렬 실행 (빠름)
Task {
    async let user = fetchUser()
    async let posts = fetchPosts()
    
    let (u, p) = await (user, posts)
    print(u, p)
}

3. Actor

기본 Actor

다음은 swift를 활용한 상세한 구현 코드입니다. 비동기 처리를 통해 효율적으로 작업을 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

actor Counter {
    private var count = 0
    
    func increment() {
        count += 1
    }
    
    func decrement() {
        count -= 1
    }
    
    func getCount() -> Int {
        return count
    }
}
// 사용
let counter = Counter()
Task {
    await counter.increment()
    await counter.increment()
    let count = await counter.getCount()
    print("Count: \(count)")  // 2
}

Actor 격리

다음은 swift를 활용한 상세한 구현 코드입니다. 비동기 처리를 통해 효율적으로 작업을 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

actor BankAccount {
    private var balance: Int
    
    init(balance: Int) {
        self.balance = balance
    }
    
    func deposit(amount: Int) {
        balance += amount
    }
    
    func withdraw(amount: Int) -> Bool {
        if balance >= amount {
            balance -= amount
            return true
        }
        return false
    }
    
    func getBalance() -> Int {
        return balance
    }
}
let account = BankAccount(balance: 1000)
Task {
    await account.deposit(amount: 500)
    let success = await account.withdraw(amount: 300)
    let balance = await account.getBalance()
    print("잔액: \(balance)")  // 1200
}

4. 실전 예제

예제: API 클라이언트

다음은 swift를 활용한 상세한 구현 코드입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며, 비동기 처리를 통해 효율적으로 작업을 수행합니다, 에러 처리를 통해 안정성을 확보합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

actor APIClient {
    private let baseURL = "https://api.example.com"
    
    func fetchUser(id: Int) async throws -> User {
        let url = URL(string: "\(baseURL)/users/\(id)")!
        let (data, _) = try await URLSession.shared.data(from: url)
        return try JSONDecoder().decode(User.self, from: data)
    }
    
    func fetchPosts(userId: Int) async throws -> [Post] {
        let url = URL(string: "\(baseURL)/users/\(userId)/posts")!
        let (data, _) = try await URLSession.shared.data(from: url)
        return try JSONDecoder().decode([Post].self, from: data)
    }
}
struct User: Codable {
    let id: Int
    let name: String
}
struct Post: Codable {
    let id: Int
    let title: String
}
// 사용
let client = APIClient()
Task {
    do {
        async let user = try client.fetchUser(id: 1)
        async let posts = try client.fetchPosts(userId: 1)
        
        let (u, p) = try await (user, posts)
        print("사용자: \(u.name)")
        print("포스트: \(p.count)개")
    } catch {
        print("에러: \(error)")
    }
}

실전 심화 보강

실전 예제: withTaskGroup으로 병렬 요청 합치기

여러 ID에 대해 동시에 네트워크 호출을 하고 결과를 배열로 모을 때 사용합니다. (아래는 로컬에서 지연만 시뮬레이션합니다.) 다음은 swift를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 클래스를 정의하여 데이터와 기능을 캡슐화하며, 함수를 통해 로직을 구현합니다, 비동기 처리를 통해 효율적으로 작업을 수행합니다, 에러 처리를 통해 안정성을 확보합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

import Foundation
func fetchName(id: Int) async throws -> String {
    try await Task.sleep(nanoseconds: 100_000_000)
    return "user-\(id)"
}
func loadAll(ids: [Int]) async throws -> [String] {
    try await withThrowingTaskGroup(of: (Int, String).self) { group in
        for id in ids {
            group.addTask {
                let name = try await fetchName(id: id)
                return (id, name)
            }
        }
        var dict: [Int: String] = [:]
        for try await (id, name) in group {
            dict[id] = name
        }
        return ids.compactMap { dict[$0] }
    }
}
@main
struct Demo {
    static func main() async throws {
        let names = try await loadAll(ids: [1, 2, 3])
        print(names)
    }
}

참고: @main 엔트리는 파일 하나에만 두어야 합니다. 기존 main과 함께 쓰려면 Task { ....} 블록으로 호출하세요.

자주 하는 실수

  • Task {} 내부에서 강한 참조로 뷰 컨트롤러를 붙잡는 경우.
  • await 없이 async 함수 결과를 기다리지 않는 경우.
  • MainActor 격리를 무시하고 UI를 백그라운드에서 갱신하는 경우.

주의사항

  • Task.sleep시간이 아니라 취소 협력점이기도 합니다. 장시간 작업은 주기적으로 Task.checkCancellation()을 호출하세요.

실무에서는 이렇게

  • 네트워크 레이어는 actor로 직렬화하고, UI는 @MainActor로 통일합니다.
  • 에러는 URLError·도메인 에러로 매핑해 사용자 메시지를 한곳에서 관리합니다.

비교 및 대안

패턴설명
async/await읽기 쉬운 제어 흐름
Combine스트림·백프레셔
GCD레거시 콜백 코드와 공존

추가 리소스


정리

핵심 요약

  1. async/await: 비동기 함수 정의 및 호출
  2. Task: 비동기 작업 생성 및 관리
  3. async let: 병렬 실행
  4. Actor: 데이터 레이스 방지
  5. Task.sleep: 비동기 대기

다음 단계


관련 글

... 996 lines not shown ... Token usage: 63706/1000000; 936294 remaining Start-Sleep -Seconds 3