관리 메뉴

김종권의 iOS 앱 개발 알아가기

[Moya] Moya프레임 워크 (RESTFul API) 본문

REST API

[Moya] Moya프레임 워크 (RESTFul API)

jake-kim 2020. 10. 24. 17:18

* 사용 방법(secret): ios-development.tistory.com/267

Moya프레임워크

* 실무적으로 쓰는 형태는 여기 참고: ios-development.tistory.com/219

  • Alamofire 프레임워크가 내부적으로 포함 (podfile.lock파일에서 확인)

  • Alamofire가 있지만 굳이 Moya프레임워크를 사용하는 이유
    • Alamofire를 사용하면, URL과 같은 것을 사용할 때 request에 넣어주어야 하며(Network Layer접근), 템플릿이 갖추어 지지 않아서 재사용에 유리하지 않은 구조
    • Moya프레임 워크를 사용하면 Moya에서 Network layer를 템플릿화 해놓고 사용하는 입장인 App에서는 request, response만 처리하면 됨

 Moya 프레임워크 의존성

pod 'Moya/RxSwift'

APIEnvironment정의 : 각 base url정의 (qa, staging, production)

//
//  NetworkManager.swift
//  Domain
//
//  Created by 김종권 on 2020/10/24.
//

import Foundation
import Moya

enum APIEnvironment: String {
    case qa         = "https://qa.com"
    case staging    = "https://staging.com"
    case production = "https://production.com"
}

struct NetworkManager {
    fileprivate let provider = MoyaProvider<AppStatusTarget>()
    static let environment: APIEnvironment = .qa
}

Request, Response모델 정의 (Response에는 json -> struct 매핑하기 쉽게 해주는 Codable사용)

//
//  MyStatusModel.swift
//  Domain
//
//  Created by 김종권 on 2020/10/24.
//

import Foundation
import UIKit

public struct MyStatusModel: Codable {
    public struct Request {
    }

    public struct Response: Codable {
        public let requireVersion: String
        public let latestVersion: String
    }
}

TargetType정의: network템플릿 작성

//
//  MyStatusTarget.swift
//  Domain
//
//  Created by 김종권 on 2020/10/24.
//

import Foundation
import Moya

public enum MyStatusTarget {
    case version
}

extension MyStatusTarget: TargetType {
    public var baseURL: URL {
        
        guard let url = URL(string: NetworkManager.environment.rawValue) else {
            fatalError("fatal error - invalid url")
        }
        return url
    }

    public var path: String {
        switch self {
        case .version:
            return "version"
        }
    }

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

    public var sampleData: Data {
        switch self {
        case .version:
            return "{\"latestVersion\":\"1.1.0\",\"minimumVersion\":\"1.0.0\"}".data(using: .utf8)!
        }
    }

    public var task: Task {
        switch self {
        case .version:
            return .requestPlain
        }
    }

    public var headers: [String: String]? {
      return ["Content-Type": "application/json"]
    }

    public var validationType: ValidationType {
      return .successCodes
    }
}

Provider를 wrapping한 클래스 생성

//
//  MyStatusAPI.swift
//  Domain
//
//  Created by 김종권 on 2020/10/24.
//

import Foundation
import Moya
import RxSwift

public class MyStatusAPI {

    let bag = DisposeBag()

    lazy var provider: MoyaProvider<MyStatusTarget> = {
        if configurations.useStub {
            return .init(stubClosure: MoyaProvider.delayedStub(0.5))
        } else {
            return .init()
        }
    }()

    public init() { }

    public func version(completion: @escaping (Result<MyStatusModel.Response, Error>) -> Void) {

        provider.rx
            .request(.version)
            .filterSuccessfulStatusCodes()
            .map(MyStatusModel.Response.self)
            .subscribe { result in
                switch result {
                case .success(let response):
                    completion(.success(response))
                case .error(let error):
                    completion(.failure(error))
                }
            }.disposed(by: bag)
    }
}

제네릭하여, result를 처리하는 방법 

  • MyStatusAPI의 다른 provider에서도, request를 json -> struct로 바꿀 때 재사용 가능하므로 제네릭으로 선언하는게 유리
//
//  MyStatusAPI.swift
//  Domain
//
//  Created by 김종권 on 2020/10/24.
//

import Foundation
import Moya
import RxSwift

enum DecodeError: Error {
    case decodeError
}

public class MyStatusAPI {

    let bag = DisposeBag()

    lazy var provider: MoyaProvider<MyStatusTarget> = {
        if configurations.useStub {
            return .init(stubClosure: MoyaProvider.delayedStub(0.5))
        } else {
            return .init()
        }
    }()

    public init() { }

    public func version(completion: @escaping (Result<MyStatusModel.Response, Error>) -> Void) {
        provider.request(.version) { result in
            self.process(type: AppVersionResponse.self, result: result, completion: completion)
        }
    }
}

extension MyStatusAPI {

    func process<T: Codable, E>(
        type: T.Type,
        result: Result<Response, MoyaError>,
        completion: @escaping (Result<E, Error>) -> Void
    ) {
        switch result {
        case .success(let response):
            if let data = try? JSONDecoder().decode(type, from: response.data) {
                completion(.success(data as! E))
            } else {
                completion(.failure(DecodeError.decodeError))
            }
        case .failure(let error):
            completion(.failure(error))
        }
    }

}

사용하는 쪽

provider.rx.request(.version)
    .filterSuccessfulStatusCodes()
    .map(MyStatusModel.Response.self)
    .subscribe { (event) in
        switch event {
        case .success(let response):
            print(response)
        case .error(let error):
            print(error)
        }
    }.disposed(by: bag)

 

정보 참고: github.com/Moya/Moya

'REST API' 카테고리의 다른 글

Postman으로 mock서버 구축하기 (REST API)  (0) 2020.10.23
Comments