가장 깔끔하고 우아한 네트워크 봉인 Moya+RxSwift를 어떻게 쓰는가

12094 단어
전언
  • Why Moya ?

  • Alamofire는 iOS Swift에서 가장 많이 사용되는 HTTP networking library일 수도 있다. Alamofire를 사용하면 NSURL Session과 그 중의 많은 번거로운 세부 사항을 추상화할 수 있어'API 관리자'와 같은 인터넷 요청을 전문적으로 관리하는 종류를 쉽게 쓸 수 있다.
    예를 들어 JSON Placeholder는 무료 테스트용 REST API입니다.
    //GET request
    let postEndpoint: String = "http://jsonplaceholder.typicode.com/posts/1"
    Alamofire.request(.GET, postEndpoint) 
      .responseJSON { response in
        //do something with response
      }
    
    //POST request
    let postsEndpoint: String = "http://jsonplaceholder.typicode.com/posts"
    let newPost = ["title": "title", "body": "body", "userId": 1]
    Alamofire.request(.POST, postsEndpoint, parameters: newPost, encoding: .JSON)
      .responseJSON { response in
        //do something with response
      }
    

    모든 요청에 대해 String 형식의 URL과 HTTP 요청 방법을 제공해야 합니다. 예를 들어.GET, 만약 당신이 완성해야 할 요청이 많다면, 코드를 쉽게 읽지 못하게 하고, 유지보수하고, 테스트할 수 있습니다.이런 문제들을 해결하는 방법은 Swift enum의 특성을 이용하여 Alamofire에router를 추가하는 것이다. 이것이 바로 모야다.
  • What is Moya ?

  • Moya는 Alamofire 기반의 Networking library이며, Reactive Cocoa와 Rx Swift에 대한 인터페이스 지원을 추가하여 개발 과정을 크게 간소화하였으며, Reactive Functional Programming의 네트워크 계층 첫 번째 선택이다.Github의 공식 소개는 Moya의 몇 가지 특징을 나열했다.
  • 컴파일할 때 API endpoint
  • 확인
  • 매거값으로 많은 endpoint를 명확하게 정의할 수 있다
  • stubResponse 유형을 추가하여 unit testing
  • 을 편리하게 하였습니다.
    본문
    본문은 먼저 모야를 어떻게 사용하는지 소개하고 두 번째 단계는 모야에 RxSwift를 추가한 다음에 데이터 층의 매핑(Model Mapping)을 추가한 다음에 이 간단한 예에 MVVM을 추가한다.한 걸음 한 걸음 차근차근 모두에게 도움이 되었으면 합니다.
    Moya
    먼저 API targets를 모두 열거하기 위해 enum를 만듭니다.이 API에 대한 모든 정보를 이 매거 유형에 넣을 수 있습니다.
    enum MyAPI {
        case Show
        case Create(title: String, body: String, userId: Int)
    }
    

    이 매거 유형은 컴파일 단계에서 모든 target에 구체적인 정보를 제공하는 데 사용되며, 매거의 값은 httprequest에 필요한 기본 인자인 url,method,parameters 등을 보내야 한다.이러한 요구는 TargetType라는 프로토콜에 정의되어 있으며 사용 과정에서 우리의 매거 유형이 이 프로토콜에 복종해야 한다.보통 우리는 이 부분의 코드를 매거 형식의 확장에 쓴다.
    extension MyAPI: TargetType {
        var baseURL: URL {
            return URL(string: "http://jsonplaceholder.typicode.com")!
        }
        
        var path: String {
            switch self {
            case .Show:
                return "/posts"
            case .Create(_, _, _):
                return "/posts"
            }
        }
        
        var method: Moya.Method {
            switch self {
            case .Show:
                return .GET
            case .Create(_, _, _):
                return .POST
            }
        }
        
        var parameters: [String: Any]? {
            switch self {
            case .Show:
                return nil
            case .Create(let title, let body, let userId):
                return ["title": title, "body": body, "userId": userId]
            }
        }
        
        var sampleData: Data {
            switch self {
            case .Show:
                return "[{\\"userId\\": \\"1\\", \\"Title\\": \\"Title String\\", \\"Body\\": \\"Body String\\"}]".data(using: String.Encoding.utf8)!
            case .Create(_, _, _):
                return "Create post successfully".data(using: String.Encoding.utf8)!
            }
        }
        
        var task: Task {
            return .request
        }
    }
    

    Moya의 사용은 매우 간단합니다. TargetType 프로토콜을 통해 모든 target을 정의한 후에 Moya를 직접 사용하여 네트워크 요청을 보낼 수 있습니다.
    let provider = MoyaProvider()
            provider.request(.Show) { result in
                // do something with result
            }
    

    + RxSwift
    Moya 자체는 사용하기에 매우 편리하고 간결하고 우아한 코드를 쓸 수 있는 네트워크 봉인 라이브러리이다. 그러나 Moya를 더욱 강하게 만드는 원인 중 하나는 Functional Reactive Programming에 대한 확장이다. 구체적으로 말하면 RxSwift와 Reactive Cocoa에 대한 확장이다. 이 두 라이브러리와의 결합을 통해 Moya를 더욱 강하게 만들 수 있다.제가 RxSwift를 선택한 이유는 두 가지가 있습니다. 하나는 RxSwift의 라이브러리가 상대적으로 경량급이고 문법 업데이트가 비교적 적습니다. 제가 전에 ReactiveCocoa를 사용한 적이 있는데 일부 큰 버전의 업데이트 수요가 많은 코드를 다시 썼습니다. 두 번째 중요한 이유는 RxSwift를 외운 후에 전체 ReactiveX의 지원이 있기 때문입니다. 그 안에 자바, JS가 포함되어 있습니다.Net, Swift, Scala, 그들 내부에는 모두 ReactiveX의 논리적 사상을 사용했다. 이것은 당신이 그 중 하나를 습득하면 나중에 ReactiveX의 다른 언어를 빨리 습득할 수 있다는 것을 의미한다.
    제 앞의 몇 편의 글에서 RxSwift의 간단한 강좌를 썼습니다. RxSwift에 익숙하지 않은 분들은 보시면 대체적으로 알 수 있습니다.Moya는 다음과 같은 RxSwift 확장성을 제공합니다.
    let provider = RxMoyaProvider()
    provider.request(.Show)
        .filterSuccessfulStatusCodes()
        .mapJSON()
        .subscribe(onNext: { (json) in
            //do something with posts
            print(json)
         })
         .addDisposableTo(disposeBag)
    
  • RxMoyaProvider는 MoyaProvider의 하위 클래스로 RxSwift에 대한 확장
  • filterSuccessfulStatusCodes()는 Moya가 RxSwift에 제공한 확장 방법으로 말 그대로 네트워크 요청을 성공적으로 수행하고 다른 것을 무시할 수 있다
  • mapJSON()도 모야 RxSwift의 확장 방법으로 되돌아오는 데이터를 JSON 형식으로 해석할 수 있다
  • subscribe는 RxSwift의 방법으로 층층이 처리된 Observable에 onNext의observer를 구독하고 JSON 형식의 데이터를 얻으면 상응하는 처리를 한다
  • addDisposableTo(disposeBag)는 RxSwift의 자동 메모리 처리 메커니즘으로 ARC와 약간 유사하여 필요하지 않은 대상을 자동으로 정리한다.

  • 프로그램을 실행하면 다음과 같은 데이터를 얻을 수 있습니다. 네트워크에서 요청한 코드는 원래 이렇게 간결하고 우아하게 쓸 수 있습니다.
    [
      {
        "userId": 1,
        "id": 1,
        "title": "sunt aut facere repellat provident occaecati excepturi optio reprehenderit",
        "body": "quia et suscipit\
    suscipit recusandae consequuntur expedita et cum\
    reprehenderit molestiae ut ut quas totam\
    nostrum rerum est autem sunt rem eveniet architecto" }, { "userId": 1, "id": 2, "title": "qui est esse", "body": "est rerum tempore vitae\
    sequi sint nihil reprehenderit dolor beatae ea dolores neque\
    fugiat blanditiis voluptate porro vel nihil molestiae ut reiciendis\
    qui aperiam non debitis possimus qui neque nisi nulla" }, { "userId": 1, "id": 3, "title": "ea molestias quasi exercitationem repellat qui ipsa sit aut", "body": "et iusto sed quo iure\
    voluptatem occaecati omnis eligendi aut ad\
    voluptatem doloribus vel accusantium quis pariatur\
    molestiae porro eius odio et labore et velit aut" }, { "userId": 1, "id": 4, "title": "eum et est occaecati", "body": "ullam et saepe reiciendis voluptatem adipisci\
    sit amet autem assumenda provident rerum culpa\
    quis hic commodi nesciunt rem tenetur doloremque ipsam iure\
    quis sunt voluptatem rerum illo velit" }, ...

    + Model Mapping
    실제 응용 과정에서 네트워크 요청은 데이터 층(Model)과 밀접하게 연결되어 있다. 구체적으로 말하자면 우리의 이 예에서 우리는 Post류를 만들어서 데이터를 통일적으로 관리해야 한다. 클래스 안에는 id,title,body 등 정보가 있고 얻은 하나의post의 JSON 데이터를 Post류, 즉 데이터 층(Model)에 비추어야 한다.
    제가 예전에 가장 많이 사용했던SwiftyJSON 이 라이브러리는 JSON의 각종 정보를 추출합니다. 이것은 Swift에서 가장 자주 사용하는 JSON을 처리하는 제3자 라이브러리입니다. 그러나 Xcode 8과 Swift 3로 업데이트된 후에 이 라이브러리는 계속 업데이트되지 않았습니다. 그래서 저는 다른 Github에도 수천 개의 star 라이브러리를 사용했습니다. 이것은 Object Mapper라고 합니다.ObjectMapper를 사용하여 Post 클래스를 만듭니다.
    class Post: Mappable {
        var id: Int?
        var title: String?
        var body: String?
        var userId: Int?
        
        
        required init?(map: Map) {
        }
        
        func mapping(map: Map) {
            id 

    자세한 ObjectMapper 강좌는 Github 홈페이지를 볼 수 있습니다. 저는 여기서 간단한 소개만 하겠습니다.ObjectMapper를 사용하려면 자신의 모델 클래스에 Mappable 프로토콜을 사용해야 합니다. 이 프로토콜에는 두 가지 방법이 있습니다.
    required init?(map: Map) {}
    
    func mapping(map: Map) {}
    
    mapping 방법 중 JSON 。데이터 클래스가 만들어진 후에 우리는 RxSwift의 Observable에 간단한 확장 방법mapObject을 써야 한다. 우리가 쓴 Post 클래스를 이용하여 한 걸음에 JSON 데이터를 하나의post로 비추어야 한다.Observable+ObjectMapper.swift라는 파일을 만들 수 있습니다.
    import Foundation
    import RxSwift
    import Moya
    import ObjectMapper
    
    extension Observable {
        func mapObject(type: T.Type) -> Observable {
            return self.map { response in
                //if response is a dictionary, then use ObjectMapper to map the dictionary
                //if not throw an error
                guard let dict = response as? [String: Any] else {
                    throw RxSwiftMoyaError.ParseJSONError
                }
                
                return Mapper().map(JSON: dict)!
            }
        }
        
        func mapArray(type: T.Type) -> Observable {
            return self.map { response in
                //if response is an array of dictionaries, then use ObjectMapper to map the dictionary
                //if not, throw an error
                guard let array = response as? [Any] else {
                    throw RxSwiftMoyaError.ParseJSONError
                }
                
                guard let dicts = array as? [[String: Any]] else {
                    throw RxSwiftMoyaError.ParseJSONError
                }
                
                return Mapper().mapArray(JSONArray: dicts)!
            }
        }
    }
    
    enum RxSwiftMoyaError: String {
        case ParseJSONError
        case OtherError
    }
    
    extension RxSwiftMoyaError: Swift.Error { }
    
  • mapObject 방법은 단일 대상을 처리하고, mapArray 방법은 대상 그룹을 처리한다.
  • 전송된 데이터response가 하나dictionary라면 ObjectMappermap 방법으로 이 데이터를 비추면 이 방법은 이전에 정의한 논리를 호출합니다.
  • mapping가 하나response가 아니라면 오류를 던져라.
  • 밑에 간단한 Error를 사용자 정의하고 Swift의 Error 클래스를 계승하여 실제 응용 과정에서 필요에 따라 원하는 Error를 제공할 수 있다.

  • 다음 프로그램을 실행합니다.
    let provider = RxMoyaProvider()
    provider.request(.Show)
        .filterSuccessfulStatusCodes()
        .mapJSON()
        .mapArray(type: Post.self)
        .subscribe(onNext: { (posts: [Post]) in
            //do something with posts
            print(posts.count)
        })
        .addDisposableTo(disposeBag)
    
    provider.request(.Create(title: "Title 1", body: "Body 1", userId: 1))
        .mapJSON()
        .mapObject(type: Post.self)
        .subscribe(onNext: { (post: Post) in
            //do something with post
            print(post.title!)
        })
        .addDisposableTo(disposeBag)
    

    결과 획득:
    100
    Title 1
    

    + MVVM
    MVVM(Model-View-ViewModel)은 데이터의 처리 논리를 ViewModel에 놓아서 ViewController의 부담을 크게 줄일 수 있으며 RxSwift에서 가장 자주 사용하는 구조 논리이다.
    이 예에서는 네트워크 요청에서 데이터를 얻는 절차를 ViewModel 파일에 쓸 수 있습니다.
    import Foundation
    import RxSwift
    import Moya
    
    class ViewModel {
        private let provider = RxMoyaProvider()
        
        func getPosts() -> Observable {
            return provider.request(.Show)
                .filterSuccessfulStatusCodes()
                .mapJSON()
                .mapArray(type: Post.self)
        }
        
        func createPost(title: String, body: String, userId: Int) -> Observable {
            return provider.request(.Create(title: title, body: body, userId: userId))
                .mapJSON()
                .mapObject(type: Post.self)
        }
    

    그런 다음 ViewController에서 ViewModel을 호출하는 방법:
    import UIKit
    import RxSwift
    
    class ViewController: UIViewController {
        
        let disposeBag = DisposeBag()
        let viewModel  = ViewModel()
        
        override func viewDidLoad() {
            super.viewDidLoad()
            // Do any additional setup after loading the view, typically from a nib.
            
            viewModel.getPosts()
                .subscribe(onNext: { (posts: [Post]) in
                    //do something with posts
                    print(posts.count)
                })
                .addDisposableTo(disposeBag)
            
            viewModel.createPost(title: "Title 1", body: "Body 1", userId: 1)
                .subscribe(onNext: { (post: Post) in
                    //do something with post
                    print(post.title!)
                })
                .addDisposableTo(disposeBag)
        }
    
        override func didReceiveMemoryWarning() {
            super.didReceiveMemoryWarning()
            // Dispose of any resources that can be recreated.
        }
        
    }
    

    글에서 이 예의 전체 항목은 Github에 놓여 있으니 다운로드하여 참고할 수 있습니다.

    좋은 웹페이지 즐겨찾기