[iOS] Async/Await

2025. 2. 6. 20:12iOS

반응형

Swift의 async/await는 비동기 작업을 더 쉽게 작성하고 관리할 수 있도록 도와주는 기능입니다. 기존의 콜백 기반 비동기 코드보다 가독성이 좋고, 오류 처리도 간편하게 할 수 있습니다.


1. async와 await란?

  • async : 비동기 함수(메서드)를 정의할 때 사용합니다.
  • await : async 함수 내부에서 비동기 작업이 완료될 때까지 기다리는 키워드입니다.

기존 클로저 기반 비동기 코드와 비교하면 가독성이 훨씬 좋아집니다.

📌 기존 콜백 방식 (Completion Handler)

func fetchData(completion: @escaping (String) -> Void) {
    DispatchQueue.global().async {
        sleep(2) // 네트워크 요청 시뮬레이션
        completion("데이터 로드 완료")
    }
}

fetchData { result in
    print(result) // "데이터 로드 완료"
}

✅ async/await 방식

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

Task {
    let result = await fetchData()
    print(result) // "데이터 로드 완료"
}

2. async 함수 선언 및 사용

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

func doSomething() async {
    print("작업 시작")
    await Task.sleep(2_000_000_000) // 2초 대기
    print("작업 완료")
}

// Task를 사용하여 실행
Task {
    await doSomething()
}
  • Task.sleep(_:)은 비동기 함수에서 일정 시간 동안 대기하도록 하는 방법입니다.
  • Task {} 내에서 await을 사용하여 비동기 함수를 호출할 수 있습니다.

3. async와 DispatchQueue.global().async의 차이점

비교항목async/awaitDispatchQueue.global().async

가독성 좋음 비교적 복잡함
오류 처리 try await로 처리 do-catch + completion handler 필요
컨텍스트 유지 Swift 런타임이 적절한 스레드에서 실행 개발자가 직접 GCD 사용
백그라운드 실행 Task {} 또는 Task.detached {} 사용 DispatchQueue.global().async 사용

예제:

// GCD 방식
DispatchQueue.global().async {
    print("백그라운드 작업 수행 중")
}

// async/await 방식
Task {
    print("백그라운드 작업 수행 중")
}

4. Task와 Task.detached

Task {}: 기존 컨텍스트를 유지

Task {
    print("Task 내부 실행")
}
  • 부모 태스크의 컨텍스트를 상속받음 (예: UI 관련 작업 시 유용)

Task.detached {}: 독립적인 실행

Task.detached {
    print("Detached Task 실행")
}
  • 기존 컨텍스트와 상관없이 별도로 실행됨
  • 네트워크 작업과 같은 독립적인 작업에 적합

5. async와 throws 함께 사용하기

비동기 함수에서 오류가 발생할 경우 throws와 함께 사용할 수 있습니다.

enum NetworkError: Error {
    case failed
}

func fetchData() async throws -> String {
    let success = Bool.random()
    if success {
        return "데이터 로드 성공"
    } else {
        throw NetworkError.failed
    }
}

Task {
    do {
        let result = try await fetchData()
        print(result)
    } catch {
        print("에러 발생: \(error)")
    }
}
  • try await을 사용하여 오류 처리를 수행할 수 있습니다.

6. async let으로 동시 실행

여러 개의 비동기 작업을 동시에 실행할 때 async let을 사용할 수 있습니다.

func fetchUserProfile() async -> String {
    sleep(2)
    return "사용자 프로필"
}

func fetchUserPosts() async -> String {
    sleep(3)
    return "사용자 게시글"
}

Task {
    async let profile = fetchUserProfile()
    async let posts = fetchUserPosts()
    
    let result = await "\(profile), \(posts)"
    print(result) // "사용자 프로필, 사용자 게시글" (약 3초 후 출력)
}
  • async let을 사용하면 병렬 실행이 가능하여 성능이 향상됩니다.
  • await을 사용하면 모든 작업이 완료될 때까지 기다립니다.

7. @MainActor와 await MainActor.run

비동기 작업 후 UI 업데이트를 위해 @MainActor를 사용할 수 있습니다.

@MainActor
class ViewModel {
    var text: String = ""
    
    func fetchData() async {
        let result = await fetchDataFromNetwork()
        text = result // UI 업데이트
    }
}

또는 특정 코드 블록에서 메인 스레드에서 실행하도록 강제할 수도 있습니다.

Task {
    let data = await fetchDataFromNetwork()
    
    await MainActor.run {
        // UI 업데이트
        print("UI 업데이트: \(data)")
    }
}

8. withCheckedContinuation으로 기존 콜백 함수 감싸기

기존 콜백 기반의 API를 async/await으로 변환할 수도 있습니다.

func fetchDataWithCallback(completion: @escaping (String) -> Void) {
    DispatchQueue.global().async {
        sleep(2)
        completion("데이터 로드 완료")
    }
}

// async/await 변환
func fetchData() async -> String {
    await withCheckedContinuation { continuation in
        fetchDataWithCallback { result in
            continuation.resume(returning: result)
        }
    }
}

Task {
    let result = await fetchData()
    print(result) // "데이터 로드 완료"
}
  • withCheckedContinuation을 사용하면 기존 비동기 API를 쉽게 변환할 수 있습니다.

9. actor와 async를 사용한 스레드 안전성 확보

actor를 사용하면 동시 접근으로 인한 문제를 방지할 수 있습니다.

actor Counter {
    private var value = 0
    
    func increment() {
        value += 1
    }
    
    func getValue() -> Int {
        return value
    }
}

let counter = Counter()

Task {
    await counter.increment()
    print(await counter.getValue()) // 1
}
  • actor 내부의 변수는 자동으로 스레드 안전하게 보호됩니다.
  • await을 사용하여 actor에 접근합니다.

정리

기능설명

async 비동기 함수 선언
await 비동기 함수 호출 시 사용
Task {} 비동기 코드 실행 (컨텍스트 유지)
Task.detached {} 독립적인 비동기 실행
throws 오류를 던질 수 있는 비동기 함수
async let 동시 실행 최적화
@MainActor UI 업데이트 시 사용
withCheckedContinuation 기존 콜백을 async/await으로 변환
actor 스레드 안전한 상태 관리

async/await을 활용하면 코드 가독성과 유지보수성이 높아지고, Swift의 동시성 지원을 최대로 활용할 수 있습니다! 🚀

반응형

'iOS' 카테고리의 다른 글

[iOS] CryptoKit vs. CryptoSwift  (0) 2025.02.14
[iOS] WKWebView Cookie  (1) 2025.02.11
[iOS] 무결성 (Integrity)  (3) 2025.02.06
[iOS] ILClassificationResponse  (1) 2025.02.03
[iOS] Performance Trace란?  (0) 2025.02.01