→ 이런 이유로 인해 RxSwift등을 통해 반환값으로 전달해 사용하는 방법으로 사용 할 수 있었으나 라이브러리를 사용하지 않는다면 여전히 불편함을 겪었었는데 이 기능을 애플에서 async/await
라는 기능을 제공하기 시작
(기존에는 iOS 15 이상에서만 제공에 현업에서 적용하기 힘든 부분이 있었으나 xcode 13.2 버전부터 iOS 13 부터 제공함)
일단 '이걸 왜 써야하는데?' 라는 생각이 든다면 코드부터 비교해보자
아래 작성하는 코드는 구글 북스 api
를 통해 검색된 책의 정보를 가져오는 프로젝트에서 검색된 책의 정보들을 비동기적으로 가져오는 코드이다
func request(api: APIable, completion: @escaping (Result<Data, APIError>) -> Void) {
guard let url = makeURL(api: api) else {
completion(.failure(APIError.urlError))
return
}
var request = URLRequest(url: url)
request.httpMethod = api.method.string
let dataTask = session.customDataTask(request: request) { result in
DispatchQueue.main.async {
guard result.error == nil else {
completion(.failure(APIError.transportError))
return
}
guard let response = result.response as? HTTPURLResponse, (200...299).contains(response.statusCode) else {
completion(.failure(APIError.responseError))
return
}
guard let data = result.data else {
completion(.failure(APIError.dataError))
return
}
completion(.success(data))
}
}
dataTask.resume()
}
뭐 사실 이렇게 이용 할 때만 하더라도 나 또한 '굳이 async/await를 사용해야 하나?' 하는 생각을 갖고 있었는데 아래 async/await를 적용한 코드를 보자
func newRequest(api: APIable) async throws -> Data {
guard let url = makeURL(api: api) else {
throw APIError.urlError
}
var request = URLRequest(url: url)
request.httpMethod = api.method.string
let result: (data: Data, response: URLResponse) = try await URLSession.shared.data(for: request)
guard let response = result.response as? HTTPURLResponse, (200...299).contains(response.statusCode) else {
throw APIError.responseError
}
return result.data
}
에러처리가 어쩌고 어플이 죽고 자시고를 떠나서 그냥 코드 양만 봐도 확연하게 줄어든 모습을 확인할 수 있다.
그럼 호출부는 어떨까?
private func getSearchInfo() {
startLoading.accept(())
let api = BookAPIModel(bookTitle: searchText, startIndex: startIndex, maxResult: maxResult, method: .get)
networkManager.request(api: api) { [weak self] result in
switch result {
case .success(let data):
do {
let searchResult = try self?.dataDecoder.parse(data: data, resultType: SearchResult.self)
guard let totalItems = searchResult?.totalItems, totalItems != 0 else {
self?.showAlert.accept("검색 결과가 없습니다")
self?.totalItems.accept(0)
self?.stopLoading.accept(())
return
}
self?.totalItems.accept(totalItems)
let oldItems = self?.items.value ?? []
let newItems = oldItems + (searchResult?.items ?? [])
self?.items.accept(newItems)
} catch let error{
self?.showAlert.accept(error.errorMessage)
}
case .failure(let error):
self?.showAlert.accept(error.errorMessage)
}
self?.stopLoading.accept(())
}
asyncTest()
}
func asyncTest() {
startLoading.accept(())
let api = BookAPIModel(bookTitle: searchText, startIndex: startIndex, maxResult: maxResult, method: .get)
Task {
do {
let data = try await networkManager.newRequest(api: api)
await MainActor.run {
guard let searchResult = try? dataDecoder.parse(data: data, resultType: SearchResult.self) else {
print("디코드 에러")
return
}
guard let totalItems = searchResult.totalItems, totalItems != 0 else {
showAlert.accept("검색 결과가 없습니다")
totalItems.accept(0)
stopLoading.accept(())
return
}
self.totalItems.accept(totalItems)
let oldItems = items.value
let newItems = oldItems + (searchResult.items ?? [])
items.accept(newItems)
}
} catch let error {
await MainActor.run {
showAlert.accept(error.errorMessage)
}
}
await MainActor.run {
stopLoading.accept(())
}
}
}
뭐... 사실 호출부에서는 코드 양으로는 그렇게 크게 줄어들지는 않았고 길이로 따지면 오히려 늘어났을 수 있는데 그건 아래와 같은 차이점 때문이다
기존 dataTask를 이용하는 방식에 비해 단점이 있다면 completionHandler 구문 작성시 UI 업데이트가 일어날 예정인 곳에서 미리 main thread에서 작동 하도록 설정이 가능했는데 async/await는 그게 어렵다는 것이다
근데 이건 그리 큰 문제는 아니다. 오히려 통신값을 보내서 계산하는 과정까지도 메인 큐에서 실행 하는 일이 발생 할 수 있게되니 딱! UI업데이트 할 때만 메인큐에서 실행할 수 있게 하는 동작시간으로는 더 나아질 수 있는 처리이다(사실 사람이 느낄 수 없는 차이겠지만...)
위 이유 때문에 UI업데이트 때문에 메인스레드에서 작동되어야 하는 코드가 추가돼서 길이가 늘어난거지 사실상 길이 자체는 차이가 없고 기존에 사용하던 DispatchQueue.main.async
키워드가 아닌 await MainActor run
키워드를 쓰면서 생기는 장점이 더 크게 다가왔다 (이 내용은 이 포스팅 에서 확인 가능)
그런데 async/await를 사용하게 된다면 네트워크 없이 테스트 하는 방법에 대해서는 더 고민을 해봐야 할 것 같다...
아무튼 async/await에 대해 처음 알았을 때는 iOS 15 버전에서만 사용이 가능하다고 해서 지금 나랑은 먼 얘기일 줄 알았는데 13 버전으로 낮춰진 시점에서 안 쓸 이유가 없다고 생각이 드는 그런 좋은... 그런 것이다! 라고 생각한다