RxSift網(wǎng)絡(luò)請求處理

前段時(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/

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容