RxSwift+Moya+ObjectMapper優雅的網路請求

ZY_FlyWay發表於2018-12-17

優雅的網路請求本人已寫了一個開源的拿來及用的[Swift專案框架](https://github.com/liuniuliuniu/LLProgramFramework.Swift) 可以參考此框架來通篇閱讀此文章更有幫助  有喜歡的還望送人玫瑰手留餘香哦

當然這個拿來及用的[Swift專案框架](https://github.com/liuniuliuniu/LLProgramFramework.Swift)有不合理的地方 還望大神指點一二

**末尾於彩蛋**

> 文章目錄

一 Rxswift 簡單介紹

二 Moya 簡單介紹以及使用

三 ObjectMapper 簡單介紹以及使用

四 RxSwift+Moya+ObjectMapper優雅的網路請求及資料轉換

一 RxSwift 簡單介紹

- RxSwift是Swift函式響應式程式設計的一個開源庫,由GitHub的ReactiveX組織開發和維護

- 其他語言像C#,Java 和JS也有: Rx.Net、RxJava、RxJS

- RxSwift的目的是讓資料/事件流和非同步任務能夠更方便的序

 列化處理 能夠使用Swift進行響應式程式設計

本文就不詳細介紹Rxswift 樓主之前寫過RxSwift 的介紹以及使用 可以參看一下 [RxSwift 個人學習筆記記錄](http://www.jianshu.com/p/00ded20182d2)

二 Moya 簡單介紹以及使用

1  Moya 簡單介紹

>[Moya](https://github.com/Moya/Moya)是一個基於Alamofire的Networking library,並且新增了對於ReactiveCocoa和RxSwift的介面支援,大大簡化了開發過程,是Reactive Functional Programming的網路層首選。

Github上的官方介紹羅列了Moya的一些特點:

* 編譯的時候會檢查API endpoint

* 可以用列舉值清楚地定義很多endpoint

* 增加了stubResponse型別,大大方便了unit testing

2  Moya 的 使用

首先我們需要宣告一個enum來對請求進行明確分類。

1

2

3

4

enum APIManager{

    case GetHomeList // 獲取首頁列表

    case GetHomeDetail(Int)  // 獲取詳情頁

}

然後我們需要讓這個enum遵守TargetType協議,在這個協議中可以看到 TargetType定義了我們傳送一個網路請求所需要的東西,baseURL,parameter,method等一些計算性屬性,我們要做的就是去實現這些東西,當然有帶預設值的我們可以不去實現。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

extension APIManager: TargetType {

    /// The target's base `URL`.

    var baseURL: URL {

        return URL.init(string: "http://news-at.zhihu.com/api/")!

    }

    /// The path to be appended to `baseURL` to form the full `URL`.

    var path: String {

        switch self {            

        case .GetHomeList: // 不帶引數的請求

            return "4/news/latest"

        case .GetHomeDetail(let id):  // 帶引數的請求

            return "4/theme/(id)"

        }

    }

// 區分get 和 post     

    var method: Moya.Method {        

        return .get

    }

    /// The parameters to be incoded in the request.

    var parameters: [String: Any]? {

        return nil

    }

    /// The method used for parameter encoding.

    var parameterEncoding: ParameterEncoding {

        return URLEncoding.default

    }

    /// Provides stub data for use in testing.

    var sampleData: Data {

        return "".data(using: String.Encoding.utf8)!

    }

    /// The type of HTTP task to be performed.

    var task: Task {

        return .request

    }

    /// Whether or not to perform Alamofire validation. Defaults to `false`.

    var validate: Bool {

        return false

    }

}

寫好上邊的以後 我們就可以去傳送一個請求了 

1

2

3

4

5

6

7

8

private let provider = RxMoyaProvider()

  // 請求資料

        provider

            .request(.GetHomeList)

            .filterSuccessfulStatusCodes()

            .mapJSON().subscribe(onNext: { (json) in                

                print(json)                

            }).addDisposableTo(bag)

 上邊就是請求資料了 回撥出來json資料

>如果對RxSwift還不熟悉的話 建議去看一下之前寫的文章[RxSwift 個人學習筆記記錄](http://www.jianshu.com/p/00ded20182d2)

Moya其實是提供了非常方面的RxSwift擴充套件 

簡單介紹一下上邊方法和變數中的各個名詞:

* RxMoyaProvider是MoyaProvider的子類,是對RxSwift的擴充套件

* filterSuccessfulStatusCodes() 是Moya為RxSwift提供的擴充套件方法,顧名思義,可以得到成功成功地網路請求,忽略其他的

* mapJSON() 也是Moya RxSwift的擴充套件方法,可以把返回的資料解析成 JSON 格式  會返回一個Observable

然後我們就可以對這個Observable進行訂閱了 

 然後我們就可以得到下邊的json資料 只展示了部分資料 

網路請求就已經結束了 就這這麼簡單輕鬆and easy ????

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

{

    date = 20170908;

    stories =     (

                {

            "ga_prefix" = 090817;

            id = 9602715;

            images =             (

                "https://pic2.zhimg.com/v2-3b39b83f560e089a7f6ddb00f6948b81.jpg"

            );

            title = "U4e24U5343U591aU5e74U524dUff0cU79e6U56fdU4e3aU4ec0U4e48U8981U7edfU4e00U4e2dU56fdUff1f";

            type = 0;

        },

                {

            "ga_prefix" = 090816;

            id = 9601590;

            images =             (

                "https://pic4.zhimg.com/v2-700a8c29d04a885354d78d5a91a9fa5b.jpg"

            );

            title = "U8fdcU5904U6765U4e86U8f66Uff0cU5148U89c1U5230U8f66U9876U5c31U8bf4U660eU5730U7403U662fU5706U7684Uff1fU4e0dU53efU80fdU7684";

            type = 0;

        },

       );

    "top_stories" =     (

                {

            "ga_prefix" = 090815;

            id = 9607829;

            image = "https://pic3.zhimg.com/v2-14c13f9f87b1b3082929b444072eebb6.jpg";

            title = "U770bU7167U7247Uff0cU6211U5c31U77e5U9053U4f60U662fU540cU6027U604bUff0cU65afU5766U798fU5927U5b66U7684U4ebaU5de5U667aU80fdU8bf4";

            type = 0;

        },

                {

            "ga_prefix" = 090807;

            id = 9606837;

            image = "https://pic1.zhimg.com/v2-cceffc2e17185ae51b7b2d14b4414e84.jpg";

            title = "U6211U8fd9U4e48U80d6Uff0cU5230U5e95U662fU56e0U4e3aU5403U5f97U592aU591aU8fd8U662fU52a8U5f97U592aU5c11Uff1f";

            type = 0;

        },

         

    );

}

三 ObjectMapper 簡單介紹以及使用

 json得到了 接下來那就是json轉模型了  

>ObjectMapper 是一個在 Swift 下資料轉模型的非常好用,並且很 Swift 的一個框架。以前我們在寫 OC 程式碼的時候用 MJExtension 轉模型,到了 Swift 的時代趕緊將 ObjectMapper 使用起來吧。

為了支援對映,類或者結構體只需要實現Mappable協議。這個協議包含兩個方法 而且這兩個方法是必須實現的

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

class LLHomeModel: Mappable {

     

    var date: String?

    var stories: [StoryModel]?

    var top_stories: [StoryModel]?

             

    //  接下來的兩個方法是必須要實現的 

    required init?(map: Map) {        

    }

    public func mapping(map: Map) {

        date <- map["date"]

        stories <- map["stories"]

        top_stories <- map["top_stories"]

    }

     

}

一旦你的物件實現了 Mappable, ObjectMapper就可以讓你輕鬆的實現和 JSON 之間的轉換。

把 JSON 字串轉成 model 物件:

1

let homeModel = LLHomeModel(JSONString: JSONString)

把一個 model 轉成 JSON 字串:

1

let JSONString = homeModel.toJSONString(prettyPrint: true)

還有一些具體的基礎使用可以參考[ObjectMapper中文翻譯](https://github.com/SwiftOldDriver/ObjectMapper-CN-Guide)

四 RxSwift+Moya+ObjectMapper優雅的網路請求及資料轉換

RxSwift結合MVVM 簡直的太合適不過了

 我們將 網路請求放在VM裡邊

1

2

3

4

5

6

7

8

9

10

    private let provider = RxMoyaProvider()

         

        // 請求資料

        provider

            .request(.GetHomeList)

            .filterSuccessfulStatusCodes()

            .mapJSON().mapObject(type: LLHomeModel.self).subscribe(onNext: { (model) in                

                self.modelObserable.value = model.stories!                

            }, onError: { (error) in                

            }).addDisposableTo(bag)

可以看到我們上邊程式碼中  `provider

            .request(.GetHomeList)

            .filterSuccessfulStatusCodes()

            .mapJSON().` 這個方法本身應該得到 JSON的 但是我後邊跟了一個mapObject 的方法  這個方式可以直接根據json的格式轉換成模型 或者是模型陣列 來看一下這個方法  我是單獨定義了一個json轉模型的類`LLToModelExtension.swift`

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

extension Observable{

    func mapObject(type: T.Type) -> Observable{

        return self.map { response in

            guard let dict = response as? [String : Any] else{

                throw RxSwiftMoyaError.ParseJSONError

            }

            return Mapper().map(JSON: dict)!

        }

    }

         

    func mapArray(type: T.Type) -> Observable<[t]> {

        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 { }

介紹一下上邊程式碼中各個方法以及名詞

* 1 `mapObject` 方法是處理單個物件的 `mapArray` 處理物件陣列

* 2 如果傳進來的資料 是一個`NSDictionary` 的話  那麼就利用 `ObjectMapper` 的 `map` 方法對映這些資料,這個方法會呼叫你之前在 mapping 方法裡面定義的邏輯。

* 3 如果 `response` 不是一個 `dictionary`, 那麼就丟擲一個錯誤。

* 4 在底部自定義了簡單的 Error,繼承了 Swift 的 Error 類,在實際應用過程中可以根據需要提供自己想要的 Error。

彩蛋

**可能會有人問 為什麼請求回來的資料 要賦值給modelObserable.Value呢  而不是賦值給一個模型陣列 然後reloadData呢**

這裡我用的RXSwift裡邊UItableView繫結資料的一個方法  再也不用寫一大串資料來源方法了 這個也可以去[這裡](http://www.jianshu.com/p/00ded20182d2)參考哦

1

2

3

4

5

6

      var modelObserable = Variable<[storymodel]> ([])

        //MARK: Rx 繫結tableView資料

        modelObserable.asObservable().bind(to: tableV.rx.items(cellIdentifier: cellID, cellType: LLHomeCell.self)){ row , model , cell in            

            cell.titleLbl.text = model.title            

            cell.imageV?.kf.setImage(with: URL.init(string: (model.images?.count)! > 0 ? (model.images?.first)! : ""))            

            }.addDisposableTo(bag)

[Swift專案框架地址](https://github.com/liuniuliuniu/LLProgramFramework.Swift

參考文章

[Moya入坑記](http://www.codertian.com/2017/01/21/iOS-moya-ru-keng-usage/)

[RxSwift+Moya](http://www.jianshu.com/p/c1494681400b)

相關文章