1. 程式人生 > >moya + RxSwift 進行網路請求

moya + RxSwift 進行網路請求

1.關於moya

如在OC中使用AFNetworking一般,Swift我們用Alamofire來做網路庫.而Moya在Alamofire的基礎上又封裝了一層:

moya

官方說moya有以下特性(我也就信了):

  • 編譯時檢查正確的API端點訪問.
  • 使你定義不同端點列舉值對應相應的用途更加明晰.
  • 提高測試地位從而使單元測試更加容易.

2.開始

1.建立列舉API

就像這樣:

enum APIManager {
    case getNewsLatest//獲取最新訊息
    case getStartImage// 啟動介面影象獲取
    case getVersion(String)//軟體版本查詢
case getThemes//主題日報列表檢視 case getNewsDetail(Int)//獲取新聞詳情 }

2.實現TargetType協議

就像這樣:

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 .getNewsLatest: return "4/news/latest" case .getStartImage://start-image 後為影象解析度,接受任意的 number*number 格式, number 為任意非負整數,返回值均相同。 return "4/start-image/1080*1776" case .getVersion(let version)://URL 最後部分的數字代表所安裝『知乎日報』的版本
return "4/version/ios/" + version case .getThemes: return "4/themes" case .getNewsDetail(let id): return "4/news/\(id)" } } /// The HTTP method used in the request. 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 } }

在這裡,可以設定請求的引數,例如url……method……para等.

3.使用

Moya的使用非常簡單,通過TargetType協議定義好每個target之後,就可以直接使用Moya開始傳送網路請求了。就像這樣:

let provider = MoyaProvider<APIManager>()
        provider.request(.getNewsLatest) { result in
            // do something with result
        }

3.配合RxSwift

Moya本身已經是一個使用起來非常方便,能夠寫出非常簡潔優雅的程式碼的網路封裝庫,但是讓Moya變得更加強大的原因之一還因為它對於Functional Reactive Programming的擴充套件,具體說就是對於RxSwiftReactiveCocoa的擴充套件,通過與這兩個庫的結合,能讓Moya變得更加強大。我選擇RxSwift的原因有兩個,一個是RxSwift的庫相對來說比較輕量級,語法更新相對來說比較少,我之前用過ReactiveCocoa,一些大版本的更新需求重寫很多程式碼,第二個更重要的原因是因為RxSwift背後有整個ReactiveX的支援,裡面包括JavaJS.Net, SwiftScala,它們內部都用了ReactiveX的邏輯思想,這意味著你一旦學會了其中的一個,以後可以很快的上手ReactiveX中的其他語言。

Moya提供了非常方面的RxSwift擴充套件:

let provider = RxMoyaProvider<APIManager>()
provider.request(.getNewsLatest)
    .filterSuccessfulStatusCodes()
    .mapJSON()
    .subscribe(onNext: { (json) in
        //do something with posts
        print(json)
     })
     .addDisposableTo(disposeBag)

解釋一下:

  • RxMoyaProviderMoyaProvider的子類,是對RxSwift的擴充套件

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

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

  • subscribe 是一個RxSwift的方法,對經過一層一層處理的 Observable 訂閱一個 onNextobserver,一旦得到 JSON 格式的資料,就會經行相應的處理

  • addDisposableTo(disposeBag)RxSwift 的一個自動記憶體處理機制,跟ARC有點類似,會自動清理不需要的物件。

4.配合HandyJSON

在實際應用過程中網路請求往往緊密連線著資料層(Model),具體地說,在我們的這個例子中,一般我們需要建立一個類用來統一管理資料,然後把得到的 JSON 資料對映到資料層(Model)。

struct MenuModel: HandyJSON {
    var others: [ThemeModel]?

}

struct ThemeModel: HandyJSON {

    var color: String?
    var thumbnail: String?
    var id: Int?
    var description: String?
    var name: String?
}

然後建立ViewModel類,建立具體請求方法:

class MenuViewModel {

    private let provider = RxMoyaProvider<APIManager>()
    var dispose = DisposeBag()

    func getThemes(completed: @escaping (_ menuModel: MenuModel) -> ()){

         provider
            .request(.getThemes)
            .mapModel(MenuModel.self)
            .subscribe(onNext: { (model) in

                completed(model)
            }, onError: { (error) in

            }, onCompleted: nil, onDisposed: nil).addDisposableTo(dispose)

    }

}

這裡解釋一下:
我這裡是將請求的資料通過閉包傳了出去,當然也可以不那麼做.個人喜好問題..

這裡是為 RxSwift 中的 ObservableTypeResponse寫一個簡單的擴充套件方法 mapModel,利用我們寫好的Model 類,一步就把JSON資料對映成 model

extension ObservableType where E == Response {
    public func mapModel<T: HandyJSON>(_ type: T.Type) -> Observable<T> {
        return flatMap { response -> Observable<T> in
            return Observable.just(response.mapModel(T.self))
        }
    }
}

extension Response {
    func mapModel<T: HandyJSON>(_ type: T.Type) -> T {
        let jsonString = String.init(data: data, encoding: .utf8)
        return JSONDeserializer<T>.deserializeFrom(json: jsonString)!
    }
}

5.配合ObjectMapper

畢竟將json資料轉換成model的庫那麼多 ….,所以……,用哪個很隨意…..這裡再介紹一下ObjectMapper

1.建立model類

class DetailModel: Mappable {

    var body = String()
    var image_source: String?
    var title = String()
    var image: String?
    var share_url = String()
    var js = String()
    var recommenders = [[String: String]]()
    var ga_prefix = String()
    var section: DetailSectionModel?
    var type = Int()
    var id = Int()
    var css = [String]()





    func mapping(map: Map) {

        body <- map["body"]
        image_source <- map["image_source"]
        title <- map["title"]
        image <- map["image"]
        share_url <- map["share_url"]
        js <- map["js"]
        recommenders <- map["recommenders"]
        ga_prefix <- map["ga_prefix"]
        section <- map["section"]
        type <- map["type"]
        id <- map["id"]
        css <- map["css"]
    }
    required init?(map: Map) {

    }
}

使用 ObjectMapper ,需要讓自己的 Model 類使用 Mappable 協議,這個協議包括兩個方法:

required init?(map: Map) {}

func mapping(map: Map) {}

mapping 方法中,用 <- 操作符來處理和對映你的 JSON資料。

資料類建立好之後,我們還需要為 RxSwift 中的 Observable 寫一個簡單的擴充套件方法 mapObject,利用我們寫好的model 類,一步就把JSON 資料對映成一個個 model

extension Observable {
    func mapObject<T: Mappable>(type: T.Type) -> Observable<T> {
        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<T>().map(JSON: dict)!
        }
    }

    func mapArray<T: Mappable>(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<T>().mapArray(JSONArray: dicts)!
        }
    }
}

enum RxSwiftMoyaError: String {
    case ParseJSONError
    case OtherError
}

extension RxSwiftMoyaError: Swift.Error { }
  • mapObject 方法處理單個物件,mapArray 方法處理物件陣列。

  • 如果傳進來的資料 response 是一個 dictionary,那麼就利用 ObjectMappermap方法對映這些資料,這個方法會呼叫你之前在 mapping方法裡面定義的邏輯。

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

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

然後執行請求方法:

class DetailViewModel {

    private let provider = RxMoyaProvider<APIManager>()

    func getNewsDetail(id: Int) -> Observable<DetailModel> {

        return provider
            .request(.getNewsDetail(id))
            .filterSuccessfulStatusCodes()
            .mapJSON()
            .mapObject(type: DetailModel.self)

    }
}

原文地址

有不對之處,,,,還望各路大神不吝指正!