Async/Await 이란?
Swift 5.5에 새롭게 추가된 기능으로 상당히 늦은감이 있지만 다뤄보려 한다. 기존의 비동기 코드를 동기처럼 동작하는 코드처럼 작성할 수 있다. 설명보다는 코드로 알아보자. 먼저 기존의 비동기 코드부터 보자면
// 선언할 때
func getSomeData(completion: [SomeData]) {
URLSession.shared.dataTask(with: url) { data, response, error in
// to do
}
.resume()
}
// 사용할 때
getSomeData { someData in
// to do
}
보통 이렇게 비동기 코드를 작성하게 되는데 문제는 이런 비동기 코드가 많으면 많아질수록 depth가 깊어지면서 가독성을 해치게 되며 이로 인해 여러 문제가 발생하게 된다.
그래서 등장한게 바로 Async/Await 이다.
앞서 예시를 들었던 URLSession처럼 SDK에 포함된 기능은 기본적으로 제공하는 Async/Await 메서드들이 있다.
바로 알아보자.
// 선언할 때
func getSomeData() async throws -> [SomeData] {
let (data, response) = URLSession.shared.data(from: url)
// to do
}
// 사용할 때
let someData = try await getSomeData()
일단 여기엔 몇가지 규칙이 있다.
async 키워드는 throws 앞에 와야하며, await 키워드는 try 뒤에 와야 한다.
물론 throws와 try 키워드를 항상 붙여야 하는건 아니고 필요없다면 생략하면 된다.
또 await 키워드가 들어간 곳에서 시스템은 함수가 해당 함수가 종료될때까지 중지 될 수 있으며, 이렇게 중지되면 스레드는 자유롭게 다른 작업을 할 수 있게 된다.
그런 다음 await의 함수가 종료되면 그 다음줄을 마저 실행하게 된다.
이미 작성된 코드를 활용하고 싶을 땐?
URLSession처럼 애플에서 Async/Await으로 된 메서드들을 제공하면 좋은데, 그렇지 않은 기능이나 기존의 작성된 수많은 비동기 코드들은 어떻게 Async/Await을 활용할 수 있을까?
답은 withCheckedThrowingContinuation을 사용하는 것이다.
바로 코드로 알아보자.
func newGetSomeData() async throws -> [SomeData] {
try await withCheckedThrowingContinuation({ continuation in
// 에러
continuation.resume(throwing: SomeError)
// 성공
continuation.resume(returning: [SomeData])
})
}
위처럼 withCheckedThrowingContinuation 를 사용하면 CheckedContinuation를 completion으로 받게 된다. 그리고 이 CheckedContinuation는 에러를 던질수도, 값을 리턴할 수도 있다.
다만 이 경우 꼭 성공이나 실패 어느 경우라도 무조건 resume를 호출해야한다. 그렇지 않으면 시스템은 영영 기다리게 되기 때문이다.
추가 팁
만약 여러 비동기 코드를 순차적으로 실행해야 하는 경우와 반대로 동시에 실행해야 하는 경우 어떻게 활용할 수 있을까? 먼저 순차적으로 실행해야 하는 경우를 보자
let id = try await getID()
let books = try await getBooks(id: id)
let favoriteBooks = try await getFavoriteBooks(books: books)
순차적인 경우는 쉽다. 그냥 순서대로 작성하면 된다. 알아서 await에서 코드가 중지되고 스레드는 다른 일을 하게 될 것이다. 그러나 만약 모든 코드를 동시에 실행하고 싶다면 어떻게 해야할까?
async let firstPhoto = downloadPhoto(named: photoNames[0])
async let secondPhoto = downloadPhoto(named: photoNames[1])
async let thirdPhoto = downloadPhoto(named: photoNames[2])
let photos = await [firstPhoto, secondPhoto, thirdPhoto]
이처럼 작성하게 되면 await 키워드에서 코드가 일시정지되고, 배열안의 모든 함수가 종료되고 값이 할당되면 다시 재개된다.