본문 바로가기
iOS

[iOS/Swift] RxMoya(Moya) ErrorHandling 해보기(feat. Repository Pattern)

by 코코종 2022. 6. 19.

안녕하세요 코코종입니다.

오늘은 회사에서 일하다가 겪은 문제를 해결한 내용을 적어보도록 하겠습니다. 찾아보니까 이게 잘 안나오더라구요 ㅜㅜ 그래서 다른분들께 혹시나 도움이 되었으면 해서 올리게 되었습니다.


먼저 문제 상황에 대한 공유하자면, 저희회사의 코드는 Repository 패턴을 활용한 MVVM으로 구성되어 있는데요. 문제는 RxMoya를 활용해서 네트워크 통신을 하게 되는데 API단에서는 Single로 return을 받는데 success인 경우와 error인 경우 ViewModel에서 처리하는 데이터의 Model이 달랐습니다. 어.. 말이 좀 길죠... 예를들면 delete를 하는데 성공일 경우에는 지울 대상의 id(:Int)를 return 하지만 delete를 할 수 없는 경우(저희는 500 error)에는 다른 모델을 던져서 errorCode와 errorMessage를 보고자 했습니다. 그러나 Repo에서 onError시에 'MoyaError' 가 아닌 'Error' 타입이 떨어지게 되어서 ViewModel에서 처리를 어떻게 해야하나 고민이 많았습니다. 

 

// APIService
    static func deleteMyPet(petId: String) -> Single<Int> {
        return provider.rx
            .request(.deleteMyPet(petId: petId))
            .filterSuccessfulStatusCodes()
            .map(Int.self)
    }


// Repository
    func deleteMyPet(petId: String) -> Observable<Int> {
      let observable = Observable<Int>.create { observer -> Disposable in
          APIService.deleteMyPet(petId: petId)
              .subscribe(onSuccess: { item in
                  observer.onNext(item)
                  observer.onCompleted()
              }, onError: { error in
                  observer.onError(error)
                  observer.onCompleted()
              }).disposed(by: self.bag)
          return Disposables.create()
      }
      return observable
    }

 

그래서 극단적으로 저부분에 대해서만 RxMoya가 아닌 일반적인 Moya로 바꿀까도 했지만 전체적인 통일성이 깨지는 문제 때문에 채택하지 않았습니다. 공식 문서와 다른 예제에서도 간단하게 같은 VC에서 사용하거나 Repository를 사용하지 않았기 때문에 어려움을 겪었습니다. 그러나 Moya를 뜯어보다보니 MoyaError가 Error의 타입인것을 활용해 타입 캐스팅을 이용하기로 했습니다. 

 

// VM
  input.deletePet
      .observeOn(backgroundScheduler)
      .flatMap{ (myPet: MyPet) -> Observable<Bool> in
        let deleteMyPet = self.myPetsRepo.deleteMyPet(petId: String(myPet.id))
        return deleteMyPet
      }
      .subscribe(onNext: { _ in
        // 성공시
      }, onError: { error in
        guard let moyaError = error as? MoyaError else { return }
        let data = moyaError.response?.data

        do {
          let result = try JSONDecoder().decode(errorModel.self, from: data)
          print(result.errorCode)
          print(result.errorMessage)
          // output에 해당 model을 추가해서 활용
        }
        catch {
          print("decoding error")
        }
      })
      .disposed(by: self.bag)
      
      
// Codable
   struct errorModel: Decodable {
    let errorCode: String
    let errorMessage: String

    enum CodingKeys: String, CodingKey {
      case errorCode
      case errorMessage
    }
  }

 

결과적으로 보면 1. Error 타입을 MoyaError 타입으로 캐스팅한다. 2. moyaError.response.data 를 do try catch문을 활용하여 JSONDecoder로 디코딩을 한다. 단 2가지 스텝이었습니다. 막상 놓고 보니까 별거아니네요 ㅎㅎ 그래도 거의 제가 몇시간을... 고생해서 알게된거랍니다 ㅜㅜ 바로 MoyaError를 넘기는게 아니다보니 고생을 좀 했네요...

 

도움이 되셨다면 하뚜를 눌러주세요! ㅎㅎ 그럼 이만!