
async & await는 기존의 비동기 처리방식인 DispatchQueue나 completionHandler보다 더욱 편리하게 비동기 처리를 할 수 있는 문법
이번 내용은 RXSwift, Combine은 제외한 async & await와 @escaping 문법을 비교해보았습니다.
@escaping 키워드를 사용한 비동기 처리 예시모든 포켓몬의 정보를 불러오는 API를 예시로 사용해봤습니다.
@escaping 키워드를 통해 completion으로 데이터를 비동기로 가져오는 코드입니다.
하지만 다양한 에러 처리를 사용하게 되면 completion을 호출하게 되는 경우가 매우 많아져 코드 흐름을 파악하기 힘들어집니다.

위에서 다뤄진 문제점들을 해결하는 async & await가 Swift 5.5 부터 등장하게 되었습니다.
어디로 튈지 모르는 비동기 코드를 동기 코드처럼 작성하고 읽을 수 있기 때문에 가독성은 물론 코드의 흐름을 파악하기 훨씬 좋아졌습니다.
위 @escaping을 이용한 함수를 async & await 문법으로 변경해보았습니다.
func fetchPokemonsWithAsyncAwait() async throws -> [Pokemon] {
guard let url = URL(string: "https://pokemon-go-api.github.io/pokemon-go-api/api/pokedex.json") else {
throw URLError(.badURL)
}
let (data, response) = try await URLSession.shared.data(from: url)
guard
let response = response as? HTTPURLResponse,
response.statusCode == 200 else {
throw URLError(.badServerResponse)
}
return try JSONDecoder().decode([Pokemon].self, from: data)
}
코드도 훨씬 줄어들었고 코드의 순서도 위에서 아래로 동기적으로 읽을 수 있게 되었습니다.
다음은 async 함수를 호출 하는 코드입니다.


fetchPokemonsWithAsyncAwait() 메서드를 호출할 경우 에러가 발생합니다. 이러한 에러가 나타나는 이유는 async 함수를 호출하기 위해서는 asynchronous context 내부에서만 호출할 수 있기 떄문입니다.

위에서 얘기했던 async 함수 호출을 위한 asynchronous context를 생성하기 위해 Task를 사용할 수 있습니다.
func fetchWithAsyncAwait() {
Task {
let pokemons = try await fetchPokemonsWithAsyncAwait()
self.pokemons = pokemons
}
}
위 코드로 데이터를 뷰를 업데이트하는 변수의 값을 변경하게 될 경우 main thread가 아닌 background thread에서 UI 업데이트가 이루어져서 문제가 발생합니다.


이러한 경우 DispatchQueue.main에서 UI 업데이트를 처리해줄 수 있는데요.
func fetchWithAsyncAwait() {
Task {
let pokemons = try await fetchPokemonsWithAsyncAwait()
DispatchQueue.main.async {
self.pokemons = pokemons
}
}
}
Swift 5.5에서 새로 추가된 @MainActor를 사용해 메서드를 main thread에서만 실행되도록 지정해줄 수 있습니다.
@MainActor
func fetchWithAsyncAwait() {
Task {
let pokemons = try await fetchPokemonsWithAsyncAwait()
self.pokemons = pokemons
}
}