버그 잡이

Swift - async / await & async let 기초 본문

Swift

Swift - async / await & async let 기초

버그잡이 2021. 7. 8. 18:31

 

이번 WWDC21에서 async / await 이 공식적으로 소개되었습니다.

swift 5.5 그리고 iOS 15부터 사용가능하지만 정식으로 소개된 만큼 이제는 알아볼 필요가 있을 것 같습니다.

 

async, await

async, await 은 "비동기 코드를 동기적으로 작성하게 해주는 swift extension" 입니다.

(말이 좀 어려운데, 기존에 비동기 코드를 작성할 경우 지저분해지는 코드를 이쁘게 만들어주는 역할을 합니다.)

 

말로는 이해가 어려우니 기존에 어떤 문제가 있었고 async, await은 어떤 장점이 있는지 코드로 한번 살펴보겠습니다.

 

 

기존 문제

 

// 1. 서버에서 기온 데이터를 받아온다
func fetchWeatherHistory(completion: @escaping ([Double]) -> Void) {
    DispatchQueue.global().async {
        let results = (1...100_000).map { _ in Double.random(in: -10...30) }
        completion(results)
    }
}

// 2. 평균을 계산한다. 
func calculateAverageTemperature(for records: [Double], 
																 completion: @escaping (Double) -> Void) {
    DispatchQueue.global().async {
        let total = records.reduce(0, +)
        let average = total / Double(records.count)
        completion(average)
    }
}

// 3. 서버에 결과를 전송한다.
func upload(result: Double, completion: @escaping (String) -> Void) {
    DispatchQueue.global().async {
        completion("OK")
    }
}

 

위와 같은 순서의 비동기 코드가 있다고 했을때, 이를 순서대로 실행시키기 위해서는 아래와 같이 completion을 사용해야 했습니다.

 

fetchWeatherHistory { records in
    calculateAverageTemperature(for: records) { average in
        upload(result: average) { response in
            print("Server response: \(response)")
        }
    }
}

 

위 코드는 클로저가 중첩된 형태로 가독성이 떨어집니다.

(물론 지금은 괜찮아 보일 수 있지만 중첩된 클로저가 더 많아지고 그 안에서 분기 처리 등의 추가 작업이 이루어진다면 가독성은 더욱 떨어질 것입니다.)

 

 

 

 

async / await 활용

 

async, await을 활용하면 보다 직관적이고 깔끔한 코드 작성이 가능해집니다.

func fetchWeatherHistory() async -> [Double] {
    (1...100_000).map { _ in Double.random(in: -10...30) }
}

func calculateAverageTemperature(for records: [Double]) async -> Double {
    let total = records.reduce(0, +)
    let average = total / Double(records.count)
    return average
}

func upload(result: Double) async -> String {
    "OK"
}

func processWeather() async {
    let records = await fetchWeatherHistory()
    let average = await calculateAverageTemperature(for: records)
    let response = await upload(result: average)
    print("Server response: \(response)")
}

각각의 함수는 비동기 함수지만 await으로 흐름을 제어함으로써 동기적인 코드로 작성이 가능해졌습니다.

클로저를 활용한 코드보다 상대적으로 가독성이 좋습니다.

 

 

 

 

 

네트워크 요청 적용 예시

 

클로저가 중첩되는 경우는 대부분 네트워크와 관련된 부분이죠. 

async, await을 활용하면 아래와 같이 네트워크 요청을 동기적으로 만들 수 있습니다.

 

override viewDidLoad() {
    super.viewDidLoad()

    async {
    	let result = await fetchUser()
    	switch result {
    	case .success(let users):
            self.users = users
    	case .failure(let error):
            print(error)
    	}
    }
}


private func fetchUsers() async -> Result<[User], Error> {
    do {
        let (data, _) = try await URLSession.shared.data(from: url)
        let users = try JSONDecoder().decode([User.self, from: data])
        return .success(users)
    } catch {
        return .failure(error)
    }
}

 

 

 

 

async let

 

이번 Concurrency 업데이트에서는 async let 이라는 기능도 추가되었습니다.

이를 활용하면 평행한 두 개의 비동기 작업을 처리하는 작업이 가능합니다.

예를 들어 두 개의 API를 각각 call 한 후 각각의 API가 모두 완료된 후에 특정 액션을 취하는 것이 있습니다.

 

아래 처럼 A, B를 기다려야 하는 경우 기존에는 dispatchGroup을 사용해야했습니다.(또는 Rx활용) 

let dispatchGroup = DispatchGroup()
dispatchGroup.enter()
dispatchGroup.enter()

NetworkManager.shared.getA(completion: { response in
    self.a = response.result
    
    dispatchGroup.leave()
})

NetworkManager.shared.getB(completion: { response in
    self.a = response.result
    
    dispatchGroup.leave()
})

dispatchGroup.notify(queue: .main) {
    let vieModel = ViewModel(self.a, self.b)
    self.navigateToNext(viewModel)
}

 

async let 을 쓰면 아래와 같이 처리 가능합니다.

func fetchA() async -> AModel {
    let A = try await KMNetworkManager.shared.getA()
    return A
}

func fetchB() async -> BModel {
    let B = try await KMNetworkManager.shared.getB()
    return B
}

// async, await 사용
func process() async {
    async let a = fetchA()
    async let b = fetchB()

    let viewModel = await ViewModel(a, b)
    self.navigateToAlarmSetting(viewModel)
}

 

(async let 관련 심화된 활용이 궁금하신 분들은 아래 글을 참조하시길 바랍니다.)

https://www.andyibanez.com/posts/structured-concurrency-in-swift-using-async-let/?utm_source=swiftlee&utm_medium=swiftlee_weekly&utm_campaign=issue_70

 

 



*참고

반응형
Comments