클라이언트

[Swift] Swift Concurrency 개념과 사용법 간단 정리

애기공룡훈련병 2025. 2. 15. 16:50
반응형

Swift 5.5부터 도입된 Swift Concurrency는 기존 GCDCombine을 대체하며, 비동기 프로그래밍을 더 직관적이고 안전하게 만들었습니다. 이 글에서는 Swift Concurrency의 핵심 개념과 사용법을 간단하게 정리합니다.

1. Swift Concurrency 개요

Swift Concurrency는 async/await, Task, actor 등을 통해 동시성 프로그래밍을 단순화하는 기능입니다. 이를 사용하면 콜백 지옥을 방지하고, 가독성이 높은 코드를 작성할 수 있습니다.

 

기존의 GCD를 사용한 코드 예제:

DispatchQueue.global().async {
    fetchData { result in
        DispatchQueue.main.async {
            updateUI(with: result)
        }
    }
}

Swift Concurrency를 활용한 코드 예제:

func loadData() async {
    let result = await fetchData()
    updateUI(with: result)
}

이처럼 async/await을 활용하면 동기 코드처럼 작성할 수 있어 가독성이 좋아집니다.

 

2. async/await의 기본 사용법

async 함수 선언

async 함수는 비동기적으로 실행될 수 있도록 선언됩니다.

func fetchUserData() async -> String {
    return "User Data"
}

await을 활용한 호출

비동기 함수는 await을 사용하여 호출해야 합니다.

func getUserInfo() async {
    let userData = await fetchUserData()
    print(userData)
}

Task를 이용한 비동기 실행

비동기 코드는 Task를 사용해 실행할 수도 있습니다.

Task {
    await getUserInfo()
}

Task 내부에서 await을 사용하면 동기 코드처럼 실행할 수 있습니다.

 

3. Task와 Task Group

Task 사용법

Task는 백그라운드에서 비동기 작업을 실행하는 구조입니다.

Task {
    let data = await fetchUserData()
    print(data)
}

Task의 우선순위 지정

우선순위를 지정하여 성능을 최적화할 수 있습니다.

Task(priority: .high) {
    let data = await fetchUserData()
    print(data)
}

Task Group을 활용한 병렬 처리

TaskGroup을 사용하면 여러 개의 비동기 작업을 동시에 실행할 수 있습니다.

func fetchMultipleUsers() async {
    await withTaskGroup(of: String.self) { group in
        for _ in 1...5 {
            group.addTask {
                return await fetchUserData()
            }
        }
    }
}

TaskGroup을 활용하면 여러 개의 비동기 작업을 한 번에 실행하고 결과를 모을 수 있습니다.

 

4. Actor를 활용한 동시성 문제 해결

기존 동시성 문제

클래스 기반의 멀티스레드 환경에서는 Race Condition이 발생할 수 있습니다.

class Counter {
    var value = 0
    func increment() {
        value += 1
    }
}

이 클래스를 여러 개의 스레드에서 동시에 호출하면 value의 상태가 불확실해질 수 있습니다.

actor를 활용한 해결 방법

Swift의 actor는 이러한 동시성 문제를 해결하는 새로운 타입입니다.

actor Counter {
    var value = 0
    func increment() {
        value += 1
    }
}

이제 Counterincrement() 메서드는 한 번에 하나의 호출만 실행되므로 Race Condition이 발생하지 않습니다.

 

5. async/await과 기존 코드 통합하기

기존 콜백 기반 함수와 통합

기존 콜백 기반의 함수를 async 함수로 변환할 수 있습니다.

func fetchData(completion: @escaping (String) -> Void) {
    DispatchQueue.global().async {
        completion("Data Loaded")
    }
}

이 함수를 async 버전으로 변환하면:

func fetchData() async -> String {
    return await withCheckedContinuation { continuation in
        fetchData { data in
            continuation.resume(returning: data)
        }
    }
}

이제 await을 사용하여 호출할 수 있습니다.

let data = await fetchData()
print(data)

6. Swift Concurrency 사용 시 주의할 점

1. MainActor를 활용한 UI 업데이트

UI 관련 코드는 반드시 메인 스레드에서 실행해야 합니다.

@MainActor func updateUI() {
    // UI 업데이트 코드
}

또는 MainActor 속성을 적용할 수도 있습니다.

@MainActor class ViewModel {
    var data: String = ""
}

2. Cancellation(작업 취소)

비동기 작업이 필요하지 않을 경우 취소할 수 있습니다.

let task = Task {
    await fetchData()
}
task.cancel()

isCancelled 프로퍼티를 사용하면 현재 작업이 취소되었는지 확인할 수 있습니다.

if Task.isCancelled {
    return
}
 

결론

Swift Concurrency를 활용하면 기존의 GCD 기반 코드보다 더 간결하고 직관적인 비동기 프로그래밍이 가능합니다. 특히 async/await, Task, actor 등을 활용하면 Race Condition을 방지하고, 코드의 가독성을 높일 수 있습니다.

다음과 같은 경우 Swift Concurrency를 고려해볼 수 있습니다.

  • 콜백 기반의 비동기 코드를 async/await으로 변환하고 싶을 때
  • Race Condition 문제를 actor로 해결하고 싶을 때
  • 여러 개의 비동기 작업을 병렬로 실행하고 싶을 때
반응형