RxSift网络请求处理

前段时间,公司项目重构,决定采用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/

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,902评论 5 468
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,037评论 2 377
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,978评论 0 332
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,867评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,763评论 5 360
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,104评论 1 277
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,565评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,236评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,379评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,313评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,363评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,034评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,637评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,719评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,952评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,371评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,948评论 2 341

推荐阅读更多精彩内容