前段時(shí)間,公司項(xiàng)目重構(gòu),決定采用RxSwift,這是一個(gè)函數(shù)式響應(yīng)編程的框架,我差不多也是提前學(xué)習(xí)了一點(diǎn),然后就邊學(xué)習(xí)邊開始了,期間也是遇到了各種問題,還好項(xiàng)目也算是按時(shí)交付測試了。這篇文章主要是用來講述RxSwift網(wǎng)絡(luò)請求的用法。
RxSwift+Moya+ObjectMapper 網(wǎng)絡(luò)請求與處理
Moya簡單的介紹
Moya是一個(gè)基于Alamofire的Networking library,并且添加了對(duì)于ReactiveCocoa和RxSwift的接口支持。
Moya使用
1.首先需要通過枚舉對(duì)請求進(jìn)行明確分類
public enum XYJHomeRouter {
/// 刷新首頁
case refreshHome(parameters: [String:Any])
/// 獲取bannar
case getBannar(parameters: [String:Any])
}
2.讓XYJHomeRouter枚舉遵守TargetType協(xié)議,這個(gè)Target便是你網(wǎng)絡(luò)請求相關(guān)行為的定義,也可以自定義協(xié)議,我們實(shí)現(xiàn)這些協(xié)議,也就相當(dāng)于完成了網(wǎng)絡(luò)請求需要的endpoint.
自定義的協(xié)議
public protocol XYJTargetType {
var isShow: Bool { get }
}
XYJHomeRouter的實(shí)現(xiàn)
extension XYJHomeRouter: TargetType,XYJTargetType {
public var baseURL: URL {
return URL(string: baseHostString)!
}
public var path: String {
switch self {
case .refreshHome:
return "yd/app/home"
case .getBannar:
return "yd/app/common/banners"
}
}
public var method: Moya.Method {
switch self {
case .refreshHome:
return .post
case .getBannar:
return .post
}
}
public var parameters: [String: Any]? {
switch self {
case .refreshHome(parameters: let dict):
return dict
case .getBannar(parameters: let dict):
return dict
}
}
public var parameterEncoding: ParameterEncoding {
return JSONEncoding.default
}
public var task: Task {
return .request
}
public var validate: Bool {
return false
}
//自己定義的協(xié)議實(shí)現(xiàn),是否顯示正在加載,有的接口在后臺(tái)請求,不需要告訴用戶
public var isShow: Bool {
switch self {
case .refreshHome:
return false
case .getBannar:
return false
}
}
3.在viewmodel中進(jìn)行網(wǎng)絡(luò)請求方法的封裝
// 獲取banner數(shù)據(jù)
func getBanner(paramaters: [String: Any]) -> Observable<XYJResultList<XYJBanner>> {
return XYJMoyaHttp<XYJHomeRouter>().sendRequest().request(.getBannar(parameters: paramaters)).mapObject(XYJResultList.self)
}
我們看下XYJMoyaHttp<XYJHomeRouter>()的實(shí)現(xiàn)
參數(shù):
EndpointClosure:可以對(duì)請求參數(shù)做進(jìn)一步的修改,如可以修改endpointByAddingParameters endpointByAddingHTTPHeaderFields等
RequestClosure:你可以在發(fā)送請求前,做點(diǎn)手腳.判斷有無網(wǎng)絡(luò)做氣泡提示 ,修改超時(shí)時(shí)間,打印一些數(shù)據(jù)等
StubClosure:可以設(shè)置請求的延遲時(shí)間,可以當(dāng)做模擬慢速網(wǎng)絡(luò)
Manager:請求網(wǎng)絡(luò)請求的方式。默認(rèn)是Alamofire
[PluginType] :Moya提供了一個(gè)插件機(jī)制,使我們可以建立自己的插件類來做一些額外的事情。比如寫Log,顯示“菊花”等。抽離出Plugin層的目的,就是把和自己網(wǎng)絡(luò)無關(guān)的行為抽離。避免各種業(yè)務(wù)揉在一起不利于擴(kuò)展
public class XYJMoyaHttp<T:TargetType> {
func sendRequest() -> RxMoyaProvider<T> {
return RxMoyaProvider<T>.init(endpointClosure: endpointClosure ,requestClosure: requestClosure,stubClosure: stubClosure,plugins: [NetworkLoggerPlugin.init(verbose: true,responseDataFormatter: {JSONResponseDataFormatter($0)}),spinerPlugin,XYJMoyaResponseNetPlugin()])
}
func sendUploadMultipart() -> RxMoyaProvider<T> {
return RxMoyaProvider<T>.init(endpointClosure: endpointClosure ,requestClosure: requestClosure ,plugins: [NetworkLoggerPlugin.init(verbose: true,responseDataFormatter: {JSONResponseDataFormatter($0)}),spinerPlugin,XYJMoyaResponseNetPlugin()])
}
// MARK: - 設(shè)置請求頭部信息
let endpointClosure = { (target: T) -> Endpoint<T> in
let url = target.baseURL.appendingPathComponent(target.path).absoluteString
let endpoint = Endpoint<T>(
url: url,
sampleResponseClosure: { .networkResponse(200, target.sampleData) },
method: target.method,
parameters: target.parameters,
parameterEncoding: target.parameterEncoding
)
//在這里設(shè)置你的HTTP頭部信息
return endpoint.adding(newHTTPHeaderFields: [
:])
}
// 發(fā)請求之前判斷是否有網(wǎng)絡(luò)
let requestClosure = { (endpoint: Endpoint<T>, done: MoyaProvider.RequestResultClosure) in
if var request = endpoint.urlRequest {
if(XYJNetworkMonitor.shareInstance.hasNetwork()) {
done(Result.success(request))
} else {
done(Result.failure(MoyaError.requestMapping(noNetWorkTipsString)))
}
}
}
/// 單元測試代碼
let stubClosure: (_ type: T) -> Moya.StubBehavior = { type1 in
return StubBehavior.never
}
}
/// 日志
///
/// - Parameter data: data數(shù)據(jù)
/// - Returns: Data數(shù)據(jù)類型
private func JSONResponseDataFormatter(_ data: Data) -> Data {
do {
let dataAsJSON = try JSONSerialization.jsonObject(with: data)
let prettyData = try JSONSerialization.data(withJSONObject: dataAsJSON, options: .prettyPrinted)
return prettyData
} catch {
return data // fallback to original data if it can't be serialized.
}
}
/// 指示燈的配置的初始化
let spinerPlugin = XYJNetworkActivityPlugin { state in
guard let currentView = XYJLogVC.instance.currentVC?.view else {
return
}
if state == .began {
XYJProgressHUD.hide(view: currentView)//失把指示燈關(guān)掉,再顯示
XYJProgressHUD.showAdded(view: currentView)
} else {
XYJProgressHUD.hide(view: currentView)
}
}
class XYJLogVC {
var currentVC: UIViewController?
//聲明一個(gè)單例對(duì)象
static let instance = XYJLogVC()
private init() {}
}
public protocol XYJTargetType {
var isShow: Bool { get }
}
網(wǎng)絡(luò)請求reposne的處理插件
可以根據(jù)返回響應(yīng)的狀態(tài)碼判斷業(yè)務(wù)成功或者失敗,還可以再這里進(jìn)行某個(gè)特殊狀態(tài)碼的全局邏輯業(yè)務(wù)處理,比如某個(gè)狀態(tài)碼,要進(jìn)行彈出登錄處理等
/// reposne的處理(net)
public final class XYJMoyaResponseNetPlugin: PluginType {
/// 成功的狀態(tài)碼
let normalCode = ["0","0000"]
//// 修改response的值
public func process(_ result: Result<Moya.Response, MoyaError>, target: TargetType) -> Result<Moya.Response, MoyaError> {
var result = result
//JSONSerialization
if case .success(let response) = result {
let processedResponse = Response(statusCode: -1, data: response.data, request: response.request, response: response.response)
guard let json = try? JSONSerialization.jsonObject(with: response.data, options: .allowFragments) as? [String:Any] , let code = json?["code"] as? String else {
return .failure(.jsonMapping(processedResponse))
}
if( !normalCode.contains(code) ) { //業(yè)務(wù)失敗
if code == "C0001" {
//清理緩存,彈出登錄框的邏輯
}
result = .failure(.statusCode(processedResponse))
} else { //業(yè)務(wù)成功
let data = json?["data"]
if let jsonDatas = data as? Data {
guard let jsonData = try? JSONSerialization.data(withJSONObject: jsonDatas, options: []) else {
return .failure(.jsonMapping(processedResponse))
}
result = .success(Response(statusCode: -1, data: jsonData, request: response.request, response: response.response))
} else {
result = .success(Response(statusCode: -1, data: response.data, request: response.request, response: response.response))
}
}
}
return result
}
4.在控制器中進(jìn)行調(diào)用
vm.getBanner(paramaters: paras as! [String : Any]).asObservable().subscribe(onNext: { (result) in
guard let bannars = result.data else {
return
}
self.vm.cacheBanner(datas: bannars)
self.bannerView.datas = bannars
}, onError: { (_) in
XYJLog(message: "獲取bannar數(shù)據(jù)失敗")
}).disposed(by: self.disposeBag)
JSON解析
我們可以單獨(dú)新建一個(gè)文件,用來對(duì)Moya的Response和ObservableType進(jìn)行擴(kuò)展
extension Response {
// 這一個(gè)主要是將JSON解析為單個(gè)的Model
public func mapObject<T: BaseMappable>(_ type: T.Type) throws -> T {
guard let json = try? JSONSerialization.jsonObject(with: self.data, options: .allowFragments) as? [String:Any] else {
throw MoyaError.jsonMapping(self)
}
guard let object = Mapper<T>().map(JSONObject:json) else {
throw MoyaError.jsonMapping(self)
}
return object
}
// 這個(gè)主要是將JSON解析成多個(gè)Model并返回一個(gè)數(shù)組,不同的json格式寫法不相同
public func mapArray<T: BaseMappable>(_ type: T.Type) throws -> [T] {
guard let json = try? JSONSerialization.jsonObject(with: self.data, options: .allowFragments) as? [String:Any] else {
throw MoyaError.jsonMapping(self)
}
guard let jsonDic = json?["data"] as? [[String: Any]] else {
throw MoyaError.jsonMapping(self)
}
guard let objects = Mapper<T>().mapArray(JSONArray: jsonDic) else {
throw MoyaError.jsonMapping(self)
}
return objects
}
}
extension ObservableType where E == Response {
// 這個(gè)是將JSON解析為Observable類型的Model
public func mapObject<T: BaseMappable>(_ type: T.Type) -> Observable<T> {
return flatMap { response -> Observable<T> in
return Observable.just(try response.mapObject(T.self))
}
}
// 這個(gè)是將JSON解析為Observable類型的[Model]
public func mapArray<T: BaseMappable>(_ type: T.Type) -> Observable<[T]> {
return flatMap { response -> Observable<[T]> in
return Observable.just(try response.mapArray(T.self))
}
}
ObjectMapper
ObjectMapper是用Swift語言實(shí)現(xiàn)對(duì)象和JSON相互轉(zhuǎn)換的框架,自定義的model需要實(shí)現(xiàn)Mappable協(xié)議,ObjectMapper可以很好的處理泛型類型的數(shù)據(jù),不過這個(gè)泛型需要實(shí)現(xiàn)Mappable協(xié)議,也可以處理好嵌套的數(shù)據(jù)結(jié)構(gòu)
下面我們看下具體的使用
原始數(shù)據(jù)
{
"code": "0",
"data": {
"expected_credit": "0",
"noread_msg": "4",
"role_type": "1",
"total_credit": "0",
"nick_name": "哦哦哦",
"wait_bill": "1",
"total_bill": "6"
},
"message": "成功"
}
數(shù)據(jù)模型處理
/// 返回結(jié)果模型
//T為泛型,遵循Mappable協(xié)議
class XYJResult<T: Mappable>: Mappable {
var code: String?
var message: String?
var data: T?
required init?(map: Map) {
}
init() {
}
func mapping(map: Map) {
code <- map["code"]
message <- map["message"]
data <- map["data"]
}
}
import UIKit
import ObjectMapper
class XYJHomeModel: Mappable {
var nickName: String?
var noReadMsg: String?
var expectedCred: String?
var totalBill: String?
var totalCredit: String?
var waitBill: String?
var monthBill: String?
//必須要實(shí)現(xiàn)的方法
required init?(map: Map) {
}
//手動(dòng)創(chuàng)建model時(shí)要寫
init() {}
//建立映射關(guān)系--
func mapping(map: Map) {
nickName <- map["nick_name"]
noReadMsg <- map["noread_msg"]
expectedCred <- map["expected_credit"]
totalBill <- map["total_bill"]
totalCredit <- map["total_credit"]
monthBill <- map["month_bill"]
waitBill <- map["wait_bill"]
}
}
參考資料:
https://github.com/Hearst-DD/ObjectMapper
http://www.codertian.com/2017/02/04/iOS-Moya-RxSwift-better-networking/