221206 TIL [Alamofire와 async/await]

Doogie·2022년 12월 6일
2
post-thumbnail

얼마전 [Alamofire 사용법 - GET] 포스팅을 통해 Alamofire의 GET에 대해 다뤄보았다

그런데 더 전에 [async/await를 이용한 비동기 통신] 포스팅을 통해 다룬 async/await도 적용을 해볼 수 있지 않을까?? 하는 생각에 여기저기 찾아보니

Alamofire Github에 swift-concurrenc에 대해 다룬 파트가 있었다

사용하는건 URLSession을 이용할 때와 크게 다르진 않지만 AF를 사용할때의 장점은 여전히 있다(decoding, URL 등등)

다만 completion을 사용하는 AF와 다른 점은 dataTask를(URLSession과는 조금 다른) 사용 해야한다는 것이다

(하지만 여전히 URLSession보다는 간단하다)

- 코드

    func newRequest<T: Decodable>(api:APIable, resultType: T.Type) async throws -> T {
        let baseURL = api.host + api.path
        let request = AF.request(baseURL, method: api.method, parameters: api.params)
        let dataTask = request.serializingDecodable(resultType)
        
        switch await dataTask.result {
        case .success(let value):
            guard let response = await dataTask.response.response, (200...299).contains(response.statusCode) else {
                throw APIError.responseError
            }
            
            return value
        case .failure:
            throw APIError.transportError
        }
    }
  1. 나는 데이터 요청을 하면서 Decoding도 같이 할 예정이기 때문에 serializingDecodable이라는 메서드를 사용했으며 이 메서드를 통해 dataTask를 만들어준다

  2. 먼저 dataTask의 result를 확인해 통신과정에서 에러가 있는지 아닌지 판단을 한다

  3. success 일때

    1. response를 체크한다
      • .failure case에는 response에 대한 에러는 없기 때문에 통신에 성공 했을 때 그 통신의 statusCode를 체크하는 용
    2. value를 return한다
  4. failure 일때

    • 에러를 던져주면 되는데 여기서 캐치하는 에러는 AF의 AFError라는 enum이다, 그래서 여기에 있는 에러 각각 모두 던져주려 했는데 너무 많아서 그냥 일단 만들어진 transportError를 던져주는 것으로 했고 추 후 어떻게 처리할지는 고민을 더 해봐야 할 것 같다

- 호출부

    private func getSearchInfo() {
        self.startLoading.accept(())
        let api = BookAPIModel(bookTitle: searchText, startIndex: startIndex, maxResult: maxResult, method: .get)
        Task {
            do {
                let searchResult = try await networkManager.newRequest(api: api, resultType: SearchResult.self)
                await MainActor.run {
                    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 = items.value
                    let newItems = oldItems + (searchResult.items ?? [])
                    self.items.accept(newItems)
                    self.stopLoading.accept(())
                }
            } catch let error {
                await MainActor.run {
                    self.showAlert.accept(error.errorMessage)
                    stopLoading.accept(())
                }
            }
        }
    }

호출부는 URLSession과 AF를 비교했을 때는 크게 달라진 점은 없다

마무리

AF의 completion handler와 acync/await를 비교했을 때는 아래와 같은 차이점이 있다

  • completion handler를 사용하는 responseDecodable메서드는 스레드 관리를 자동으로 해주는 반면 acync/await를 사용하는 serializingDecodable는 UI업데이트 부에서 쓰레드 지정을 해줘야 한다
  • serializingDecodable는 호출부에서 ARC관리를 따로 해줄 필요가 없다

위 두 내용으로 정리를 해보자면 개인적으로 비동기 메서드는 UI업데이트가 되기 직전까지는 비동기로 진행되어야 최대로 효율을 낼 수 있지 않을까? 하는 입장으로 서는 async/await가 좋다고 생각이 들지만 불편하긴 한 것 같다 일일이 다 해줘야 하니...

그래도 비동기값을 completion을 사용하지 않고 return값으로 사용할 수 있다는건 엄청나게 큰 메리트라고 생각이 든다!

프로젝트 링크

profile
끊임없이 문을 여는 개발자

0개의 댓글