moya + RxSwift 進行網路請求
1.關於moya
如在OC中使用AFNetworking一般,Swift我們用Alamofire來做網路庫.而Moya在Alamofire的基礎上又封裝了一層:
官方說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
的擴充套件,具體說就是對於RxSwift
和ReactiveCocoa
的擴充套件,通過與這兩個庫的結合,能讓Moya
變得更加強大。我選擇RxSwift
的原因有兩個,一個是RxSwift
的庫相對來說比較輕量級,語法更新相對來說比較少,我之前用過ReactiveCocoa
,一些大版本的更新需求重寫很多程式碼,第二個更重要的原因是因為RxSwift
背後有整個ReactiveX
的支援,裡面包括Java
,JS
,.Net
, Swift
,Scala
,它們內部都用了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)
解釋一下:
RxMoyaProvider
是MoyaProvider
的子類,是對RxSwift
的擴充套件filterSuccessfulStatusCodes()
是Moya
為RxSwift
提供的擴充套件方法,顧名思義,可以得到成功地網路請求,忽略其他的mapJSON()
也是Moya RxSwift
的擴充套件方法,可以把返回的資料解析成JSON
格式subscribe
是一個RxSwift
的方法,對經過一層一層處理的Observable
訂閱一個onNext
的observer
,一旦得到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
中的 ObservableType
和 Response
寫一個簡單的擴充套件方法 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
,那麼就利用ObjectMapper
的map
方法對映這些資料,這個方法會呼叫你之前在mapping
方法裡面定義的邏輯。如果
response
不是一個dictionary
, 那麼就丟擲一個錯誤。在底部自定義了簡單的
Error
,繼承了Swift
的Error
類,在實際應用過程中可以根據需要提供自己想要的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)
}
}
有不對之處,,,,還望各路大神不吝指正!