Networking은 앱에서 거의 필수적인 요소다.
Networking을 위해 개발자는 흔히 APIManager 또는 NetworkModel로 불리는 네트워크 모듈을 설계한다.
이런 상황에 API추가가 쉽고 변경이 용이한 네트워크 모듈을 개발하는것은 중요하다.
네트워크 통신을 위해 URLSession을 사용할 수 있다. 하지만 Moya 라이브러리 README에 적혀있듯 여러 단점이 존재한다.
1. 새로운 앱 작성이 어려워진다. ("어디서부터 시작해야하지?)
2. 기존 앱 유지관리가 어려워진다. ("OMG.. 난장판")
3. 단위 테스트의 작성이 어려워진다
그래서 많은 개발자는 Alamofire를 사용할 것이다.
하지만 유지보수와 유닛테스트가 힘들다는 단점이 있어 이를 조금 더 추상화시켜 탄생한 것이 Moya라고 한다.
Moya는 Alamofire를 한번 더 추상화해서 네트워크 계층을 템플릿화해 재사용성을 높이고 가독성을 높여 개발자는 Response와 Request에만 신경을 쓸 수 있도록 하여 생산성을 높여준다.
Moya의 README에서 장점은 이렇게 설명한다.
1. 컴파일시 API 엔드 포인트가 올바른지 체크
2. Enum을 이용해서 언제, 어디에 사용될지 안전하게 정의
3. 유닛테스트의 용이함.
사용방법.
1. Enum을 사용해서 사용할 API목록들을 작성한다.
enum을 사용해 보다 안전하게 사용할 수 있고 유지보수의 관점으로 볼 때 새로운 API추가가 편리해진다.
enum UserTarget {
case login(oAuthProvider: OAuthProvider, accessToken: String)
case getUser(accessToken: String)
case refreshToken
}
2. TargetType을 구현해주어야한다.
위에서 작성한 enum으로 작성한 API목록을 extension하는데, TargetType프로토콜을 comfirm하여 API별로 리퀘스트에 필요한 것들을 지정해야한다.
- baseURL : Server base URL 지정
- path : API Path 지정
- method : HTTP Method 지정 (Get, Post..)
- task : Request에 사용될 파라미터
- validation Type: 허용할 response의 타입
- sampleData: 테스트용 Mock Data
- headers : HTTP Header
extension UserTarget : TargetType {
var baseURL: URL { URL(string: ServiceAPI.baseURL)! }
var path: String {
switch self {
case .logIn(oAuthProvider: let type, accessToken: _) : return "/login/oauth/" + type.rawValue
case .refreshToken : return "/token"
}
}
var method: Moya.Method {
switch self {
case .logIn(oAuthProvider: _, accessToken: _) : return .post
case .refreshToken : return .get
}
}
// var sampleData: Data { ... }
var task: Task {
switch self {
case .logIn(oAuthProvider: _, accessToken: let accessToken) :
let params : [String: String] = [ "accessToken" : accessToken ]
return .requestParameters(parameters: params, encoding: URLEncoding.default )
case .refreshToken : return .requestPlain
}
}
var headers: [String : String]? {
switch self {
case .logIn(oAuthProvider: _, accessToken: _) : return nil // [ "Content-type": "application/json" ]
case .refreshToken :
guard let userInfo = UserService.shared.userInfo else { return [ "Content-type": "application/json" ] }
return [ "Content-type": "application/json", "X-AUTH-TOKEN" : userInfo.token.tokenType + " " + userInfo.token.accessToken ]
}
}
var validationType: ValidationType { .successCodes }
}
3. ResponseStruct생성
받아온 Response를 저장할 구조체가 될 것이다.
struct OAuthLoginResponse : Codable, Equatable {
var userId : Int
var name : String
var email : String
var imageUrl : String
var role : String
var token : Token
}
4. MoyaProvider생성
네트워크 요청을 수행할 Moya Provider인스턴스를 생성한다.
MoyaProvider는 제네릭 타입으로 Target Type 프로토콜을 준수하는 enum을 받는다.
private let provider = MoyaProvider<UserTaget>()
5. Network요청 / 처리
위에 생성한 provider를 통해 request를 할 수 있다.
provider의 request의 파라미터로 TargetType을 전달하면 된다. 이때 반환되는 response는 Result<Response, MoyaError>형태이다.
provider.request(.logIn(oAuthProvider: .Kakao, accessToken: accessToken)) { response in
switch response {
case .success(let result) :
guard let data = try? result.map(DataResponse<OAuthLoginResponse>.self) else { return }
print(data)
case .failure(let err):
print(err.localizedDescription)
}
}
6. RxSwift
Rx익스텐션은 두 가지 메서드를 제공한다
- rx.request(:callbackQueue:) -> Single<Response>
- rx.requestWithProgress(:callbackQueue:) -> Observable<ProgressResponse>
예를 들어 오류를 처리하는 과정이다.
provider = MoyaProvider<GitHub>()
provider.rx.request(.userProfile("ashfurrow")).subscribe { event in
switch event {
case let .success(response):
image = UIImage(data: response.data)
case let .error(error):
print(error)
}
}
Moya를 사용해보면 생각보다 간편함과 가독성이 매우 좋아진다고 느꼈다.