Moya : github.com/Moya/Moya
대표적인 네트워킹 라이브러리로는 Alamofire, Moya가 있는데요, 오늘은 Moya에 대해서 배워보도록 하겠습니다.
Alamofire와 Moya는 둘 다 외부 네트워킹 라이브러리이지만, 둘은 목적이 다릅니다.
추상화 프레임워크인 Moya를 알아보도록 하겠습니다.
인터넷에 접근하기 위해 다음과 같은 구조를 거칠 것입니다.

다음과 같은 구조는, 화살표의 시작과 끝이 자유롭다는 점에서 긍정적이라고 할 수 있으나(..)
네트워킹이 필요한 모든 액션이나 뷰에서 네트워킹을 호출한다면 이른바 콜백 헬에 이를 수 있습니다.
이를 해결하기 위해서는 조금 수고를 해서라도 일일히 비동기처리를 해준다던지,
비동기 프레임워크인 Rx를 사용한다는 방법이 있을 수 있지만
Moya에서는 다음과 같은 구조로 해결하기를 제안합니다. ( RxSwift도 지원함 RxMoya도 있음 )

네트워킹 레이어가 훨신 정돈되었습니다.
위와 같이 정리된 네트워킹 흐름은 세가지 단점을 극복시켜 주었습니다.
- 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)
1
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 | |
} |
열거체를 하나 만들었는데, 이름 `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 | |
} | |
} |
다음과 같이 처리할 수 있습니다.
오늘은 여기까지입니다! 질문과 오류 지적은 언제나 감사히 받고 있습니다.
'iOS' 카테고리의 다른 글
[iOS] 애플로그인 - 개인정보 얻기, private email relay (2) | 2020.10.13 |
---|---|
[iOS] Bundle과 Package에 대해서 (0) | 2020.09.18 |
[iOS] Swift Attributes를 배워보자 (0) | 2020.09.04 |
[iOS] Dependency Injection, (IoC, DI, DIP) (0) | 2020.08.21 |
[iOS] 객체지향과 SOLID 원칙 - Swift (0) | 2020.08.14 |