美文网首页
RxSift网络请求处理

RxSift网络请求处理

作者: 星星点灯0_0 | 来源:发表于2017-12-25 10:50 被阅读0次

    前段时间,公司项目重构,决定采用RxSwift,这是一个函数式响应编程的框架,我差不多也是提前学习了一点,然后就边学习边开始了,期间也是遇到了各种问题,还好项目也算是按时交付测试了。这篇文章主要是用来讲述RxSwift网络请求的用法。

    RxSwift+Moya+ObjectMapper 网络请求与处理

    Moya简单的介绍

    Moya是一个基于Alamofire的Networking library,并且添加了对于ReactiveCocoa和RxSwift的接口支持。

    Moya使用

    1.首先需要通过枚举对请求进行明确分类

    public enum XYJHomeRouter {
        /// 刷新首页
        case refreshHome(parameters: [String:Any])
        /// 获取bannar
        case getBannar(parameters: [String:Any])
    }
    

    2.让XYJHomeRouter枚举遵守TargetType协议,这个Target便是你网络请求相关行为的定义,也可以自定义协议,我们实现这些协议,也就相当于完成了网络请求需要的endpoint.
    自定义的协议

    public protocol XYJTargetType {
        var isShow: Bool { get }
    }
    

    XYJHomeRouter的实现

    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
        }
        //自己定义的协议实现,是否显示正在加载,有的接口在后台请求,不需要告诉用户
        public var isShow: Bool {
            switch self {
            case .refreshHome:
                return false
            case .getBannar:
                return false
            }
        }
    

    3.在viewmodel中进行网络请求方法的封装

      // 获取banner数据
        func getBanner(paramaters: [String: Any]) -> Observable<XYJResultList<XYJBanner>> {
            return XYJMoyaHttp<XYJHomeRouter>().sendRequest().request(.getBannar(parameters: paramaters)).mapObject(XYJResultList.self)
        }
    

    我们看下XYJMoyaHttp<XYJHomeRouter>()的实现
    参数:
    EndpointClosure:可以对请求参数做进一步的修改,如可以修改endpointByAddingParameters endpointByAddingHTTPHeaderFields等
    RequestClosure:你可以在发送请求前,做点手脚.判断有无网络做气泡提示 ,修改超时时间,打印一些数据等
    StubClosure:可以设置请求的延迟时间,可以当做模拟慢速网络
    Manager:请求网络请求的方式。默认是Alamofire
    [PluginType] :Moya提供了一个插件机制,使我们可以建立自己的插件类来做一些额外的事情。比如写Log,显示“菊花”等。抽离出Plugin层的目的,就是把和自己网络无关的行为抽离。避免各种业务揉在一起不利于扩展

    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: - 设置请求头部信息
        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
            )
            //在这里设置你的HTTP头部信息
            return endpoint.adding(newHTTPHeaderFields: [
    
                :])
        }
    
    
        // 发请求之前判断是否有网络
        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数据
    /// - Returns: Data数据类型
    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)//失把指示灯关掉,再显示
            XYJProgressHUD.showAdded(view: currentView)
        } else {
            XYJProgressHUD.hide(view: currentView)
        }
    }
    
    class XYJLogVC {
        var currentVC: UIViewController?
        //声明一个单例对象
        static let instance = XYJLogVC()
        private init() {}
    }
    
    public protocol XYJTargetType {
    
        var isShow: Bool { get }
    }
    
    网络请求reposne的处理插件

    可以根据返回响应的状态码判断业务成功或者失败,还可以再这里进行某个特殊状态码的全局逻辑业务处理,比如某个状态码,要进行弹出登录处理等

    /// reposne的处理(net)
    public final class XYJMoyaResponseNetPlugin: PluginType {
        /// 成功的状态码
        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) ) {  //业务失败
                    if code == "C0001" {
                      //清理缓存,弹出登录框的逻辑
                    }
                    result = .failure(.statusCode(processedResponse)) 
                } else {   //业务成功
                    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.在控制器中进行调用

      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数据失败")
            }).disposed(by: self.disposeBag)
    

    JSON解析

    我们可以单独新建一个文件,用来对Moya的Response和ObservableType进行扩展

    extension Response {
        // 这一个主要是将JSON解析为单个的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
        }
    
        // 这个主要是将JSON解析成多个Model并返回一个数组,不同的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 {
        // 这个是将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))
            }
        }
    
        // 这个是将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语言实现对象和JSON相互转换的框架,自定义的model需要实现Mappable协议,ObjectMapper可以很好的处理泛型类型的数据,不过这个泛型需要实现Mappable协议,也可以处理好嵌套的数据结构
    下面我们看下具体的使用
    原始数据

    {
        "code": "0",
        "data": {
            "expected_credit": "0",
            "noread_msg": "4",
            "role_type": "1",
            "total_credit": "0",
            "nick_name": "哦哦哦",
            "wait_bill": "1",
            "total_bill": "6"
        },
        "message": "成功"
    }
    

    数据模型处理

    /// 返回结果模型
    //T为泛型,遵循Mappable协议
    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?
        //必须要实现的方法
        required init?(map: Map) {
        }
        //手动创建model时要写
        init() {}
       //建立映射关系--
        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/

    相关文章

      网友评论

          本文标题:RxSift网络请求处理

          本文链接:https://www.haomeiwen.com/subject/kybrwxtx.html