티스토리 뷰

반응형

이전 글: Entity 및 모델 구현하기
관련 Git Pull Request: KakaoAPI와 통신할 네트워크 기능을 구현해요
 

[DaumCafeSearch 앱] Entity 및 모델 구현하기

이전 글: 앱 구조 설계하기 관련 Git Pull Request: https://github.com/helloworldjay/daumcafesearch/pull/7 [DaumCafeSearch 앱] 앱 구조 설계하기 이전 글: 프로젝트를 시작하며 구조 설계하기 앞선 글에서..

jayb-log.tistory.com

 

NetworkManager 설계하기

 

Kakao API와 통신을 담당할 NetworkManager 객체를 구현해요. 

 

현업에 가기 전까지는 URLSession으로만 기능 구현을 했었어요. DataTask를 통해서 통신하는 기능을 구현하고, Mock 객체를 통해 테스트를 진행하기 위해 의존성을 주입하는 구조로 설계했었어요.

 

현업에서의 코드는 Alamofire로 구현이 되어있었어요.

프로젝트 코드에서 하나의 네트워킹 매니저를 완벽하게 구현할 일보다는 Feature Flag를 추가해 국가 분기를 한다거나, 피드 정보를 불러올 때 어떤 요소를 추가적으로 더 받아오는 작업처럼 기존 코드 기능에 추가하는 형태로 작업했었기 때문에 구조를 읽고 이해하는 것이 더 중요했었어요. 

 

그래서 이번 프로젝트는 Alamofire를 사용해 직접 구현하는 것을 연습해보려고 해요. 

 

 

Network Test

 

제가 속했던 팀에서는 TDD를 진행했어요. 테스트를 엄밀하게 설계하고, 만약 코드가 완벽하더라도 테스트 코드의 설계가 잘못되었다면 PR Approve를 받을 수 없는 형태로 작업했어요. 

 

하지만 네트워킹 테스트는 진행하지 않았어요. 앞서 언급한것처럼 네트워킹을 처음부터 끝까지 구현할 일이 거의 없고, VIP 구조였기 때문에 NetworkWorker 객체를 만들어 통신을 하며 기존에 존재하는 API 객체에 필요한 정보를 추가해서 사용하는 작업이 많았었기 때문에 콘솔만으로도 통신 여부를 확인할 수 있었어요.

 

GitHub에서 여러 iOS 프로젝트를 봤을 때 네트워킹 테스트를 실제 통신하여 비동기로 결과를 체크하는 테스트도 많았어요. 하지만 테스트의 기본은 어떤 환경에서도 테스트가 가능해야 하고, 그렇기 때문에 네트워크 통신이 가능하지 않은 상황에서도 테스트 가능해야 한다고 생각해요. 

 

이번 프로젝트의 목표 자체는 우선 Alamofire를 사용해보는 것이고, 테스트 역시 Mock으로 작성해보면 좋겠지만 오히려 테스트는 Moya를 적용해볼까 생각중이에요. 

 

Moya에서 간편하게 제공하는 테스트 방식 역시 경험해보는 것이 목표예요.

 

 

Request 만들기

 

request를 구현하는 방식은 여러가지일 수 있는데, 저의 경우 URLRequestConvertible을 채택하는 enum을 만들어 구현했어요.

 

Kakao API의 경우 인증 키를 headers로 관리해요.

그래서 headers에 값을 추가했어요.

 

또 URLRequestConvertible를 채택하면 asURLRequest 메서드를 만들어줘야 해요.

그런데 영화 정보를 가져오는 다른 예시 프로젝트 코드를 봤을 때는 get이 단순히 주소만 입력하면 값을 가져오기 때문에 외부에서 request에 값을 주입할 필요가 없었어요. 

 

하지만 이 프로젝트의 경우 입력받은 검색어를 GET으로 넘겨줘야하기 때문에 asURLRequest 메서드에서 그 검색어를 받아야 해요. 처음에는 단순히 parameter로 받으면 될까 했는데 당연히 그렇게 되면 채택해서 구현해야 하는 asURLRequest()와 다른 메서드가 되기 때문에 해법이 아니었어요.

 

그래서 case를 정의할 때 String을 함께 받도록 정의했어요. 키워드를 입력받아 parameter에 그 키워드를 넣어주는 구조로 구현했어요.

 

🤔 case의 이름 정하기

HTTP 통신에서 GET 방식으로 값을 가져오기 때문에 .get~~ 로 네이밍 하는 경우가 많아요. 하지만 습관 때문인지, Swift에서 메서드를 만들 때 get으로 네이밍을 시작하는 것을 지양하므로 case 이름을 fetch로 결정했어요. get으로 시작하는 게 조금 더 직관적일 수 있지만 HTTPMethod에서 .get을 return 하기 때문에 충분히 이해 가능할 것이라고 판단했어요.

 

 

NetworkManager 구현하기

 

우선, 의존성 주입을 위해 프로토콜을 구현하고 채택하게 구현했어요.

 

상속을 받아야 할 필요가 없고, 객체 타입일 필요가 없이 인스턴스를 생산해 작동하는 객체이기 때문에 struct로 구현했어요.

 

문제는 response 부분이었어요.

예시에서 본 것처럼 responseJSON 메서드를 사용해서 구현을 했는데 노란색 에러 메시지가 떴어요.

 

 

responseJSON이라는 메서드가 deprecated 되었다는 메시지였어요. 심지어 Alamofire 6에서 없어질 거라는 메시지였죠. 그래서 메시지에서 추천하는 대로 responseDecodable을 쓰기 위해 문서를 검색했어요.

 

기존에 responseJSON을 사용할 때에는 response를 JSON을 포함한 result로 가져왔기 때문에 클로저 내부에서 Decoder를 통해 decoding 해줬어야 했어요. 아래가 기존의 코드예요.

 

// 변경 전
func fetchDaumCafeList(with keyword: String, completion: @escaping (DaumCafeSearchResponse) -> Void) {
    session.request(NetworkRequestRouter.fetchDaumCafeArticle(keyword)).validate().responseJSON { response in
      switch response.result {
      case .success(let value):
        do {
          let jsonData = try JSONSerialization.data(withJSONObject: value, options: .prettyPrinted)
          
          let decoder = JSONDecoder()
          let dateFormatter = DateFormatter().then {
            $0.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.sssZ"
          }
          decoder.dateDecodingStrategy = .formatted(dateFormatter)
          
          let daumCafeSearchResponse = try decoder.decode(DaumCafeSearchResponse.self, from: jsonData)
          completion(daumCafeSearchResponse)
        } catch(let error) {
          print(error)
        }
      case .failure(let error):
        print(error)
      }
    }
  }

 

그런데 이름에서 보이듯 responseDecodable은 decoding 기능을 내장했을 것이라고 추측했어요.

문서를 통해 parameter를 확인하고, 코드를 아래와 같이 변경했어요.

 

// 변경 후
func fetchDaumCafeList(with keyword: String, completion: @escaping (DaumCafeSearchResponse) -> Void) {
    
    let decoder = JSONDecoder()
    let dateFormatter = DateFormatter().then {
      $0.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.sssZ"
    }
    decoder.dateDecodingStrategy = .formatted(dateFormatter)
    
    session.request(NetworkRequestRouter.fetchDaumCafeArticle(keyword))
      .validate()
      .responseDecodable(of: DaumCafeSearchResponse.self, decoder: decoder) { response in
        switch response.result {
        case .success(let value):
          completion(value)
        case .failure(let error):
          print(error)
        }
      }
  }

 

decoding 과정에서 문제가 발생해서 수정하는데 시간을 썼었는데, 해결 과정에 관한 글은 아래 글에서 작성했어요.

 

[iOS] Kakao API 사용시 Date 타입이 iso8601일 때 iOS에서 Decoding이 안되는 경우

문제 상황 이해하기 카카오 API를 활용해서 JSON을 받아오고, iOS에서 decoding 하려고 하는데 문제가 발생했어요. 우선 아래는 Kakao API 개발 가이드에 나오는 내용이에요. datetime의 타입은 Datetime이고,

jayb-log.tistory.com

반응형
댓글