본문 바로가기

iOS

[iOS] Moya에 대해서 공부해보아요

Moya : github.com/Moya/Moya

대표적인 네트워킹 라이브러리로는 Alamofire, Moya가 있는데요, 오늘은 Moya에 대해서 배워보도록 하겠습니다.

Alamofire와 Moya는 둘 다 외부 네트워킹 라이브러리이지만, 둘은 목적이 다릅니다.

추상화 프레임워크인 Moya를 알아보도록 하겠습니다.

 

인터넷에 접근하기 위해 다음과 같은 구조를 거칠 것입니다.

[사진 1]

다음과 같은 구조는, 화살표의 시작과 끝이 자유롭다는 점에서 긍정적이라고 할 수 있으나(..)

네트워킹이 필요한 모든 액션이나 뷰에서 네트워킹을 호출한다면 이른바 콜백 헬에 이를 수 있습니다.

이를 해결하기 위해서는 조금 수고를 해서라도 일일히 비동기처리를 해준다던지,

비동기 프레임워크인 Rx를 사용한다는 방법이 있을 수 있지만

Moya에서는 다음과 같은 구조로 해결하기를 제안합니다. ( RxSwift도 지원함 RxMoya도 있음 )

 

[그림 2]

네트워킹 레이어가 훨신 정돈되었습니다.

위와 같이 정리된 네트워킹 흐름은 세가지 단점을 극복시켜 주었습니다.

  • Makes it hard to write new apps ("where do I begin?")
  • Makes it hard to maintain existing apps ("oh my god, this mess...")
  • Makes it hard to write unit tests ("how do I do this again?")

또한, Moya의 훌륭한 세가지 특징은

  • Compile-time checking for correct API endpoint accesses.
  • Lets you define a clear usage of different endpoints with associated enum values.
  • Treats test stubs as first-class citizens so unit testing is super-easy.

위와 같습니다.

Moya와 Alamofire의 차이는 무엇인가 묻는다면, 가장 간단하게는

 

Moya는 직접적인 네트워킹을 수행하지 않음 (자체적 네트워킹을 수행하지 않는다)

Alamofire는 직접적인 네트워킹을 수행함

 

으로 정리할 수 있겠습니다.

그렇다면, Moya로 Alamofire의 네트워킹 기능을 사용하고, Alamofire을 추상화하기 위한 수단인 것이겠죠?

 

오늘의 Moya 설명에서 필요한 것은 크게 두가지입니다.

 

1. 

열거체 정의

 

2.

Request는 `MoyaProvider`가 담당하고 있습니다. 이때 사용하는 파라미터는 TargetType입니다.

예제코드 설명(www.raywenderlich.com/5121-moya-tutorial-for-ios-getting-started

Moya는 열거형을 사용해서 타입이 안전한 방식(Type-Safe)으로 네트워킹을 요청합니다. 열거체를 만들어 줍니다.

import Moya
public enum Marvel {
// 1
static private let publicKey = "YOUR PUBLIC KEY"
static private let privateKey = "YOUR PRIVATE KEY"
// 2
case comics
}
view raw moyaEnum.swift hosted with ❤ by GitHub

열거체를 하나 만들었는데, 이름 `Marvel`에 관련된 통신을 이 열거체에서 진행할 것입니다.

case comics에서

comics는 어느 연관값도 가지고 있지 않은 한 API Service가 됩니다. 이를 구현하기 위해서는

comics에 필요한 Url, Path, method, header와 task 등의 기본적 규약을 구성해야 합니다.

 

* 1에서 필요한 공개키와 프라이빗 키는 예제코드를 따라가면 받을 수 있습니다.

 

 

1 - 1

extension으로 열거체에 속성을 부여합니다. 또한, `TargetType`을 채택해서 필요한 속성들을 구현해주어야 합니다.

TargetType에서 제공하는 속성은 다음과 같습니다.

 

  • baseURL: 서버의 Base URL
  • path: API 주소. baseURL 뒤에 경로 형태로 붙음.
  • method: HTTP method (GET, POST, …)
  • sampleData: 테스트용 Mock이나 Stub
  • task: 리퀘스트에 사용되는 파라미터 설정
  • validationType: 허용할 response의 타입
  • headers: HTTP header
extension Marvel: TargetType {
// 1
public var baseURL: URL {
return URL(string: "https://gateway.marvel.com/v1/public")!
}
// 2
public var path: String {
switch self {
case .comics: return "/comics"
}
}
// 3
public var method: Moya.Method {
switch self {
case .comics: return .get
}
}
// 4
public var sampleData: Data {
return Data()
}
// 5
public var task: Task {
return .requestPlain // TODO
}
// 6
public var headers: [String: String]? {
return ["Content-Type": "application/json"]
}
// 7
public var validationType: ValidationType {
return .successCodes
}
}

나름 직관적으로 해석이 가능합니다.

3번 블록에서, comics는 get 타입이므로 get입니다.

만약 다른 API가 필요하다면 enum 정의에서 새로운 친구를 정의하고 (ex) case export(param: MovieViewModel)

movie case에 해당하는 경우를 extension에서 추가해주면 되겠습니다. (ex) case .export: return .post

이렇게 1에 해당하는 정의부 구현을 끝냈습니다. 확장성과 편리함, 협업시에 가독성까지 잡을 수 있을 것 같습니다.

 

그럼 이제 실제로 사용을 하러 가겠습니다.

2. MoyaProvider를 이용하기 : Request 요청하기

MoyaProvider 인스턴스를 선언 후, 

let provider = MoyaProvider<Marvel>()
provider.rx.request(.comics)
.subscribe { [weak self] (event) in
switch event {
case .success(let response):
self?.handleSuccessResponse(response)
case .error(let error):
print(error.localizedDescription)
}
}
.disposed(by: disposeBag)

MoyaProvider의 인스턴스 제네릭 타입으로 Marvel을 지정해주고, 

request의 파라미터로는 comics 타입을 지정하고 있습니다!

 

데이터를 받아서, handleSuccessResponse(response) 타입에서 뷰와 바인딩해주면 작업은 완료가 됩니다!

 

Rx가 아닌 기본적인 코드는

let provider = MoyaProvider<Marvel>()
// 1
state = .loading
// 2
provider.request(.comics) { [weak self] result in
guard let self = self else { return }
// 3
switch result {
case .success(let response):
do {
// 4
print(try response.mapJSON())
} catch {
self.state = .error
}
case .failure:
// 5
self.state = .error
}
}

다음과 같이 처리할 수 있습니다.

 

오늘은 여기까지입니다! 질문과 오류 지적은 언제나 감사히 받고 있습니다.