Swift 비동기 프로그래밍과 동시성 처리하기: DispatchQueue와 Async/Await 활용

작성일 :

Swift 비동기 프로그래밍과 동시성 처리하기: DispatchQueue와 Async/Await 활용

비동기 프로그래밍과 동시성 처리는 현대 애플리케이션 개발에서 매우 중요한 요소입니다. Swift에서는 이를 위해 다양한 도구와 패턴을 제공하며, 그 중에서도 DispatchQueueAsync/Await는 가장 핵심적인 역할을 합니다. 이 글에서는 이 두 가지 도구에 대해 자세히 살펴보고, 이를 활용하여 효율적이고 빠른 애플리케이션을 개발하는 방법을 알아보겠습니다.

DispatchQueue: 시작하기

DispatchQueue는 Grand Central Dispatch(GCD)의 일부분으로, 작업을 비동기적으로 실행할 때 사용하는 추상화 도구입니다. DispatchQueue는 기본적으로 두 종류가 있습니다:

  • Serial DispatchQueue: 한 번에 하나의 작업만 실행됩니다.
  • Concurrent DispatchQueue: 여러 작업을 동시에 실행할 수 있습니다.

DispatchQueue를 사용하면 복잡한 스레드 관리를 직접 하지 않아도 되므로 관련 오류를 줄일 수 있습니다. 간단한 예제를 통해 이를 더 잘 이해해 보겠습니다.

swift
let serialQueue = DispatchQueue(label: "com.example.serialQueue")

serialQueue.async {
    print("작업 1 시작")
    sleep(2)
    print("작업 1 종료")
}

serialQueue.async {
    print("작업 2 시작")
    sleep(1)
    print("작업 2 종료")
}

위 코드에서 serialQueue는 두 개의 작업을 순차적으로 실행합니다. 첫 번째 작업이 종료된 후에야 두 번째 작업이 시작됩니다. 반면, Concurrent DispatchQueue는 다음과 같이 동작합니다:

swift
let concurrentQueue = DispatchQueue(label: "com.example.concurrentQueue", attributes: .concurrent)

concurrentQueue.async {
    print("작업 1 시작")
    sleep(2)
    print("작업 1 종료")
}

concurrentQueue.async {
    print("작업 2 시작")
    sleep(1)
    print("작업 2 종료")
}

concurrentQueue는 두 개의 작업을 동시에 실행하며, 각 작업은 독립적으로 실행됩니다. 이 때문에 작업 2가 작업 1보다 먼저 종료될 수 있습니다.

Async/Await: 더 나은 비동기 프로그래밍

Swift 5.5에서는 더욱 직관적이고 관리하기 쉬운 비동기 코드를 작성할 수 있는 Async/Await 문법이 도입되었습니다. Async/Await는 기존의 콜백 기반 비동기 코드의 복잡성을 줄여줍니다.

아래는 Async/Await를 사용한 예제입니다:

swift
func fetchData() async -> String {
    // 네트워크 요청 시뮬레이션
    await Task.sleep(2 * 1_000_000_000)
    return "데이터 로드 완료"
}

@main
struct MyApp {
    static func main() async {
        let result = await fetchData()
        print(result)
    }
}

위 예제에서 fetchData 함수는 비동기로 동작하며, 데이터 로드를 시뮬레이션하기 위해 Task.sleep을 사용하고 있습니다. main 함수에서 이 비동기 함수를 호출할 때 await 키워드를 사용하여 결과를 기다립니다. 이는 마치 동기 함수처럼 보이지만 실제로는 비동기로 동작합니다.

DispatchQueue와 Async/Await 혼합 사용하기

여러 상황에서 DispatchQueueAsync/Await를 함께 사용하면 더욱 강력한 비동기 프로그래밍을 구현할 수 있습니다. 예를 들어, 특정 작업을 백그라운드에서 실행하면서 UI 작업은 메인 스레드에서 유지해야 하는 경우를 생각해봅시다.

swift
func performBackgroundTask() async {
    await withCheckedContinuation { continuation in
        DispatchQueue.global().async {
            // 백그라운드 작업 수행
            sleep(2)
            continuation.resume(returning: "작업 완료")
        }
    }
}

@main
struct MyApp {
    static func main() async {
        let result = await performBackgroundTask()
        await MainActor.run {
            print("메인 스레드에서 결과 처리: \(result)")
        }
    }
}

이 코드의 performBackgroundTask 함수는 DispatchQueue.global().async를 사용하여 백그라운드 작업을 수행한 후, withCheckedContinuation을 사용해 비동기 계산의 결괏값을 await 키워드로 기다립니다. 이 기능은 더 복잡한 비동기 시나리오를 단순하게 구현할 수 있게 합니다.

결론

Swift에서 비동기 프로그래밍과 동시성 처리를 위해 DispatchQueueAsync/Await를 활용하는 방법을 알아보았습니다. DispatchQueue는 적절한 스레드 관리와 동시성 제어를 가능하게 해주며, Async/Await는 비동기 코드를 더욱 읽기 쉽고 관리하기 쉽게 만들어줍니다. 이 두 가지 도구를 적절히 조합하면 복잡한 비동기 프로그램도 효율적으로 구현할 수 있습니다. Swift로 개발하는 모든 애플리케이션에서 이러한 도구들을 적극 활용해 보세요.