본문 바로가기

TIL

[TIL] Moya 첫 적용기(feat. 사용법)

적용 동기

이전에는 Moya의 TargetType을 직접 구성하여 Router를 구성하였지만 이번 새로 프로젝트를 진행하며 Moya 라이브러리를 적용해보기로 하였습니다. 

Moya란?

여러 블로그들을 참고한 결과 결론적으로 Moya가 탄생하게 된 배경엔 손쉬운 네트워크 통신과 용이한 테스트 코드 작성 때문이라고 합니다.

 

Moya 공식 문서에는 아래와 같이 서술되어 있습니다.

the basic idea of Moya is that we want some network abstraction layer that sufficiently encapsulates actually calling Alamofire directly. It should be simple enough that common things are easy, but comprehensive enough that complicated things are also easy.

 

영어라서 어질어질하지만 간단히 살펴보자면 Moya는 Alamofire를 한번더 추상화하였으며, 일반적인 것들(common things)과 복잡한 것들 모두 쉬워야(easy)한다고 합니다. Moya는 뭔가 추상화를 통해 더욱 쉬운 네트워크 통신을 목적으로 만들어진 것 같습니다.

 

사용법

Target 구성

Moya의 사용의 Target 구성으로부터 시작된다고 해도 과언이 아닙니다.

우선 공식 문서부터 살펴보도록 하겠습니다.

enum CoingeckoAPI {
    case trending
}

 

공식 문서의 예시인데요. 우선 열거형으로 API에서 수행하려는 작업에 대해 선언해줍니다.

 

extension CoingeckoAPI: TargetType {
    var baseURL: URL { URL(string: APIKeys.baseURL)! }
}

 

그 다음 Moya에서 제공되는 TargetType 프로토콜을 준수하고 baseURL을 선언해 줍니다.

 

var path: String {
    switch self {
    case .trending:
        return "/search/trending"
    }
}

 

그 다음으로 Endpoint를 정의해줍니다.

 

var method: Moya.Method {
    switch self {
    case .trending:
        return .get
    }
}

 

API End point별 Method를 정의해줍니다.

 

var task: Moya.Task {
    switch self {
    case .trending:
        return .requestPlain
    }
}

 

 

자 이제 task를 정의해줍니다. 이 task에서 URL 파라미터와 HTTP Body 부분에 들어갈 내용들을 정의해줍니다.

 

추가적으로 task 프로퍼티는 데이터를 보내고 받는 방법을 나타내며 요청 본문에 데이터, 파일 및 스트림을 추가할 수 있습니다.

다음은 공식 문서에 있는 요청 타입들을 나타낸 것입니다

 

 

var headers: [String : String]? {
    switch self {
    case .trending:
        return ["Content-Type": "application/json"]
    }
}

 

마지막으로 HTTP Header를 정의해줍니다.

 

네트워크 에러 구성

enum NetworkErrors: Error {
    case networkError
    case unknownError
}

 

네트워크 통신에 대한 에러를 구성해줍니다.

 

Provider 구성

struct CoingeckoManager {
    
    static var cancellable = Set<AnyCancellable>()
    
    static func fetchTrending() -> AnyPublisher<[TrendingItem], NetworkErrors> {
        Future<[TrendingItem], NetworkErrors> { promise in
            let provider = MoyaProvider<CoingeckoAPI>() // Provider 구성
            provider.requestPublisher(.trending) // Provider를 통한 네트워크 요청
                .sink { completion in
                    switch completion {
                    case .finished:
                        print("Successfully Fetch Trending!!")
                    case .failure(let error):
                        print("Fetch Trending Error", error.localizedDescription)
                    }
                } receiveValue: { response in
                    do {
                        let data = try response.filterSuccessfulStatusCodes().data
                        let result = try JSONDecoder().decode(Trending.self, from: data)
                        promise(.success(result.coins))
                    } catch {
                        print("Network Error")
                        promise(.failure(.networkError))
                    }
                    promise(.failure(.unknownError))
                }.store(in: &cancellable)
        }.eraseToAnyPublisher()

    }
}

 

자 이제 실제 사용입니다. Provider를 구성하여 위와 같이 네트워크 통신을 진행해주면 됩니다.

 

전체 코드

struct CoingeckoManager {
    
    static var cancellable = Set<AnyCancellable>()
    
    static func fetchTrending() -> AnyPublisher<[TrendingItem], NetworkErrors> {
        Future<[TrendingItem], NetworkErrors> { promise in
            let provider = MoyaProvider<CoingeckoAPI>()
            provider.requestPublisher(.trending)
                .sink { completion in
                    switch completion {
                    case .finished:
                        print("Successfully Fetch Trending!!")
                    case .failure(let error):
                        print("Fetch Trending Error", error.localizedDescription)
                    }
                } receiveValue: { response in
                    do {
                        let data = try response.filterSuccessfulStatusCodes().data
                        let result = try JSONDecoder().decode(Trending.self, from: data)
                        promise(.success(result.coins))
                    } catch {
                        print("Network Error")
                        promise(.failure(.networkError))
                    }
                    promise(.failure(.unknownError))
                }.store(in: &cancellable)
        }.eraseToAnyPublisher()

    }
}

enum NetworkErrors: Error {
    case networkError
    case unknownError
}

enum CoingeckoAPI {
    case trending
}

extension CoingeckoAPI: TargetType {
    var baseURL: URL { URL(string: APIKeys.baseURL)! }
    
    var path: String {
        switch self {
        case .trending:
            return "/search/trending"
        }
    }
    
    var method: Moya.Method {
        switch self {
        case .trending:
            return .get
        }
    }
    
    var task: Moya.Task {
        switch self {
        case .trending:
            return .requestPlain
        }
    }
    
    var headers: [String : String]? {
        switch self {
        case .trending:
            return ["Content-Type": "application/json"]
        }
    }
}

 

마무리

사실 Moya 사용의 진가는 테스트 코드 작성에 있어 오는 것 같은데 이 부분에 대해선 생략하여 오늘 포스팅에는 조금 아쉬움이 남는 것 같습니다. 다음에 기회가 되면 이에 대해 추가적인 작성도 진행해 보도록 하겠습니다.

 

여기까지 글 읽어주셔서 진심으로 감사합니다~!

 

참고 사이트

https://github.com/Moya/Moya (Moya 공식 사이트)

https://techblog.woowahan.com/2704/ (우아한 기술 블로그 - Moya)

https://medium.com/@jongchanko/swift-using-combine-moya-alamofire-to-handle-request-and-response-da72dd2f2baa (사용법 참고 블로그 1)

https://velog.io/@parkgyurim/iOS-Moya  (사용법 참고 블로그 2)

https://medium.com/verywords-ios-team/moya-7a43be086ed4 (사용법 참고 블로그 3)