ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Apollo swift + Rxswift 를 활용한 API 네트워크와 에러처리
    개발/Swift 2022. 11. 3. 12:57

    ApolloClient 구현

    Network 라는 싱글톤 클래스를 만들고

    ApolloClient 객체를 생성해야한다.

    final class Network {
        static let shared = Network()
        
        private(set) lazy var apollo: ApolloClient = {
            guard let url = URL(string: APIURL),
            let uuid = UIDevice.current.identifierForVendor?.uuidString else { fatalError("Create Apollo Client Error")}
    
            let store = ApolloStore()
    
            let interceptorProvider = NetworkInterceptorsProvider(
                       interceptors: [UUIDInterceptor(uuid: uuid)],
                       store: store
                   )
           let networkTransport = RequestChainNetworkTransport(interceptorProvider: interceptorProvider, endpointURL: url)
            return ApolloClient(networkTransport: networkTransport, store: store)
    
        }()
    }
    

    위와같이 ApolloClient 객체를 생성해준다.

    헤더에 UUID 를 넣어주기 위해

    InterceptorProvider가 필요했고

    InterceptorProvider 를 사용하기 위해 RequestChainNetworkTransport객체를 생성해서 사용했다.

    만약 헤더가 필요없다면 단순히 ApolloClient(url:url) 로 생성 가능하다.

    이후부터는 Network.shared.apollo 로 접근해 Apolloclient 함수를 호출할수있다.

    Query & Mutation 호출 (Datasource 클래스)

    func fetchProductList(callBack: @escaping (Result<GraphQLResult<ProductListQuery.Data>, Error>) -> Void ) {
            let query = ProductListQuery(id_list: nil)
            Network.shared.apollo.fetch(query: query, cachePolicy: .fetchIgnoringCacheCompletely, contextIdentifier: nil, queue: .main) { fetchResult in
                callBack(fetchResult)
            }
        }
    
    func createProduct(input: CreateProductInput, callBack: @escaping (Result<GraphQLResult<CreateProductMutation.Data>, Error>) -> Void) {
            let mutation = CreateProductMutation(input: input)
            Network.shared.apollo.perform(mutation: mutation, publishResultToStore: true, queue: .main) { result in
                callBack(result)
            }
        }
    

    Network.shared.apollo 객체에 접근하여 간단히 fetch() , perform() 함수를 호출 할수있다.

    쿼리는 fetch를 사용하고 뮤테이션은 perform을 사용한다.

    네트워크 응답에대한 처리는 콜백으로 처리할수 있다.

    응답 값 성공 실패 처리 (뷰모델)

    private let productList = PublishSubject<[ProductListQuery.Data.ItemList]>()
    
    private func fetchProductList() {
            dataSource.fetchProductList {[weak self] fetchResult in
                switch fetchResult {
                case .success(let result):
                    if let error = result.errors?.first as? Error {
                        self?.productList.onError(error)
                    }
                    if let fetchList = result.data?.productList.itemList {
                        self?.productList.onNext(fetchList)
                    }
                case .failure(let error):
                    self?.productList.onError(error)
    
                }
            }
        }
    

    네트워크 응답값은 아까 예시로 Result<GraphQLResult<ProductListQuery.Data>, Error>

    이런식으로 되어있다. 즉 응답 값은 GraphQLResult 혹은 Error 로 나뉜다.

    switch 문을 활용해 success / failure 로 분기 처리 할수있다.

     

    이때 알아야 할 점은 서버에서 보내준 에러인경우 failure가 아닌 sucess로 떨어진다는 점이다.

    예로 Mutation 의 인풋값이 잘못되었다던지, 쿼리가 잘못 되었다던지 등의 에러는

    success안으로 떨어지며 네트워크 에러같은 에러는 failure로 떨어진다.

     

    즉 서버에서 어떠한 응답이라도 받았다면 sucess 로 떨어진다.

    이때 응답이 에러라면 에러의 대한 타입은 GraphQLError 라는 타입이다.

    그래서 Error 타입으로 업캐스팅 하여 onError() 에 넣어줄수있다.

    만약 데이터를 성공적으로 응답 받았다면 onNext()를 통해 스트림에 넣어준다.

    응답값 사용 (viewcontroller)

    output.productList
              .catch({ error in
                  self.present(Dialog.getDialog(title: "에러", message: error.localizedDescription), animated: true)
                  return Observable.just([])
              })
              .bind(to: productListTableView.rx.items(cellIdentifier: "ProductListCell", cellType: ProductListTableViewCell.self)) {[weak self] (_, element, cell) in
                  cell.configure(id: element.id, nameKo: element.nameKo, nameEn: element.nameEn, price: element.price, supplier: element.supplier?.name)
                  self?.loadingView?.removeFromSuperview()
              }
              
              .disposed(by: disposeBag)
    

    위의 output.productList 는 Observable이고 아까 서버로부터 받아온 리스트를 받아온다.

    Observable의 경우 Error 가 떨어지면 Disposed 되는 특징이있다.

    그래서 위와같이 catch를 작성해줌으로써 dispose 되는것을 막을수 있으며

    이때 해당 에러에 대한 내용을 사용할수 있다.

     

    error를 다시 GraphQLError 타입으로 캐스팅하여 message든 에러 코드든 받아올수잇다.

    반대로 성공적으로 받아온경우 bind()를 통해 데이터를 사용해줄수 있다.

    댓글

Designed by Tistory.