RxSwift 网络请求

一、说明
入坑RxSwift 有段时间了,之前在项目中只是小范围的使用RxSwift,为了更好的使用响应式编程,决定在项目中更广范围的使用RxSwift,然后研究了一下RxSwift的网络请求,现在有关网络请求的案例大多是基于RXSwift(4.0.0)或者更早的库来写的,本篇文章是基于目前最新的版本(4.2.0)版本来写的,由于RxSwift 版本的更新,里面的使用语法,发生了变化,在整理的过程中遇到了一些问题,为了让后来学习的小伙伴,节约时间,决定记录下来
二、网络请求
1.使用RxSwift相关库的版本
ObjectMapper (3.2.0)
HandyJSON (4.1.1)
Moya (11.0.2)
RxCocoa (4.2.0)
RxSwift (4.2.0)
2.在Swift语言中,我们使用Alamofire 作为网络库,moya 是对Alamofire 更抽象一层的封装,RxSwift把Moya封装后作为网络请求的接口,我们在使用的时候只需要实现 TargetType 协议就好,用一个例子来看下怎么使用:
import Foundation
import Moya

enum APIService{
    case  mainClassList
}

extension APIService:TargetType{

    var baseURL: URL {
       return URL(string:"http://cmsadmin.fotoable.net")!
    }
    
    var path: String {
        switch self {
        case .mainClassList:
             return "/sandboxColor/category"
        }
    }
    
    var method: Moya.Method {
        switch self {
        case .mainClassList:
             return .get
        }
    }
    
    var parameters: [String : Any]? {
        
        switch self {
        case .mainClassList:
            return nil
        }
    }
    
    var parameterEncoding: ParameterEncoding {
        
        return URLEncoding.default
    }
    
    var sampleData: Data {
        return "{}".data(using: String.Encoding.utf8)!
    }
    
    var task: Task {
        return .requestPlain
    }
    
    var headers: [String : String]? {
        return nil
    }
}

首先,我们定义了一个 枚举 APIService ,作用主要是在内部定义网络请求的接口,然后,就是对协议 TargetType进行扩展,我们一一解读下里面的参数
baseURL:网络请求的基本URL
path:用于匹配具体网络请求接口
method:网络请求方式,常用就是 get/post 两种
parameters:接口请求时要带的参数
parameterEncoding:参数编码方式(这里使用URL的默认方式)
sampleData:这里用于单元测试
task:执行网络请求的任务
validationType:是否执行Alamofire验证,默认值为false
headers:网络请求时需要的header,如果和后台没有特殊的验证处理,默认传nil 就可以
APIService 作为网络请求的统一接口,里面封装了网络请求所需的一些基本数据

3.在进行网络请求之前,需要做一些准备工作,把网络请求回的数据通过JSON 转化成 Model , 这里我们使用了两种方式进行转换(根据项目的情况,灵活选择使用),一种通过 ObjectMapper

库进行转换,一种是通过 HandyJSON 库 进行转换 ,分别通过对 Response 类 扩展 ,以下是对这两种方式的封装
其一:使用 ObjectMapper库 把JSON 转换成 Model

import Foundation
import RxSwift
import Moya
import ObjectMapper

// MARK: - Json -> Model
extension Response {
    
    func mapObjectModel<T: BaseMappable>(_ type: T.Type, context: MapContext? = nil) throws -> T {
        guard let object = Mapper<T>(context: context).map(JSONObject: try mapJSON()) else {
            throw MoyaError.jsonMapping(self)
        }
        return object
    }
    
    func mapObjectArray<T: BaseMappable>(_ type: T.Type, context: MapContext? = nil) throws -> [T] {
        guard let array = try mapJSON() as? [[String : Any]] else {
            throw MoyaError.jsonMapping(self)
        }
        return Mapper<T>(context: context).mapArray(JSONArray: array)
    }
}

// MARK: - Json -> Observable<Model>

extension ObservableType where E == Response {
    // 将Json解析为Observable<Model>
    public func mapObjectModel<T: BaseMappable>(_ type: T.Type) -> Observable<T> {
        return flatMap { response -> Observable<T> in
            return Observable.just(try response.mapObjectModel(T.self))
        }
    }
    // 将Json解析为Observable<[Model]>
    public func mapObjectArray<T: BaseMappable>(_ type: T.Type) -> Observable<[T]> {
        return flatMap { response -> Observable<[T]> in
            return Observable.just(try response.mapObjectArray(T.self))
        }
    }
}

其二 : 使用 HandyJSON 库 把JSON 转化成 Model

import Foundation
import RxSwift
import Moya
import HandyJSON

extension ObservableType where E == Response {
    public func mapHandyJsonModel<T: HandyJSON>(_ type: T.Type) -> Observable<T> {
        return flatMap { response -> Observable<T> in
            return Observable.just(response.mapHandyJsonModel(T.self))
        }
    }
}

extension Response {
    func mapHandyJsonModel<T: HandyJSON>(_ type: T.Type) -> T {
        let jsonString = String.init(data: data, encoding: .utf8)
        if let modelT = JSONDeserializer<T>.deserializeFrom(json: jsonString) {
            return modelT
        }
        return JSONDeserializer<T>.deserializeFrom(json: "{\"msg\":\"请求有误\"}")!
    }
}

4.在MainClassViewModel中,使用已经封装好的接口进行网络请求,代码如下:
import RxSwift
import Moya
import ObjectMapper
import HandyJSON
import RxCocoa

class MainClassViewModel {

    private let provider = MoyaProvider<APIService>()
    let disposeBag = DisposeBag()
    var dataSource = BehaviorRelay<[MainClassModelMapObject_sub]>(value:[])
    var networkError = BehaviorRelay(value: Error.self)
}


//MARK: -- 网络
extension  MainClassViewModel {
    
    //网络请求-- ObjectMapper
    func getClassListWithMapObject(){
        provider.rx.request(.mainClassList).asObservable().mapObjectModel(MainClassModelMapObject.self).subscribe({ [unowned self] (event) in
            
            switch event {
            case let  .next(classModel):
                print("ObjectMapper -- 加载网络成功")
                self.dataSource.accept(classModel.data)
                
            case let .error( error):
                print("error:", error)
                self.networkError.accept(error as! Error.Protocol)
            case .completed: break
            }
        }).disposed(by: self.disposeBag)
    }
    
    
    //网络请求-- HandyJSON
    func getClassListWithMapHandyJson(){
        provider.rx.request(.mainClassList).asObservable().mapHandyJsonModel(MainClassModel.self).subscribe({ [unowned self] (event) in
            
            switch event {
            case let  .next(classModel):
                
                print("HandyJSON -- 加载网络成功")
                
            case let .error( error):
                print("error:", error)
               self.networkError.accept(error as! Error.Protocol)
            case .completed: break
            }
        }).disposed(by: self.disposeBag)
    }
    
}

这里用了两种方式,分别对 mainClassList API 接口进行了网络请求,唯一不同的是,在得到到网络请求回来数据的时候,一个是使用 mapObjectModel 把JSON 转化成 Model ,一个是使用 mapHandyJsonModel 把 JSON转化成Model ,由于我们使用的是不同的库,把JSON 转化成 Model,这两种实现的方式还是有一些差别,下面是这两种 Model 的具体实现方式:

其一、实现协议 Mappable

import UIKit
import ObjectMapper

class MainClassModelMapObject: Mappable {
    
    var code:NSInteger?
    var data:[MainClassModelMapObject_sub]!
    
    required init?(map: Map) {}
    
    func mapping(map: Map) {
        code <- map["code"]
        data <- map["data"]
    }
}

class MainClassModelMapObject_sub: Mappable {
    
    var ID:String?
    var name:String?
    var desc:String?
    var imgUrl:String?
    var gifUrl:String?
    var isUpdate:Bool?
    var backgroundGroup:NSInteger?
    
    required init?(map: Map) {}
    
    func mapping(map: Map) {
        
        ID <- map["ID"]
        name <- map["name"]
        desc <- map["desc"]
        imgUrl <- map["imgUrl"]
        gifUrl <- map["gifUrl"]
        isUpdate <- map["isUpdate"]
        backgroundGroup <- map["backgroundGroup"]
    }
}

其二、实现协议 HandyJSON

import UIKit
import HandyJSON

struct MainClassModel: HandyJSON {

    var code:NSInteger?
    var data:[MainClassModel_sub]!
}

struct MainClassModel_sub: HandyJSON {
    
    var ID:String?
    var name:String?
    var desc:String?
    var imgUrl:String?
    var gifUrl:String?
    var isUpdate:Bool?
    var backgroundGroup:NSInteger?
}

5、以上是使用 RxSwift 进行网络请求的分析,接下来看一个示例如何使用,在MainClassViewModel 中我们使用 dataSource 保存了网络请求回来的数据,我们要在 ViewController里 用tableview 把这个数据展示出来,需要提前把数据源和TableView进行绑定,以下是示例代码:
  //cell
     viewModel.dataSource.bind(to: tableView.rx.items) { (tableView, row, element) in
            let cell = tableView.dequeueReusableCell(withIdentifier: "MainClassTableViewCell", for: IndexPath(row: row, section: 0)) as! MainClassTableViewCell
            
            cell.setModel(model: element)
            // configure cell
            return cell
            }
            .disposed(by: disposeBag)

在需要使用的地方,调用 方法 getClassListWithMapObject() 或者 getClassListWithMapHandyJson()

三、总结

这部分的内容,适合对RxSwift 有一定了解的小伙伴学习, 文章重点是 帮助大家学习和了解 RxSwift 网络请求的相关知识,下面是一个写好的demo

demo

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

推荐阅读更多精彩内容

  • 1、通过CocoaPods安装项目名称项目信息 AFNetworking网络请求组件 FMDB本地数据库组件 SD...
    X先生_未知数的X阅读 15,937评论 3 118
  • 看完了大冰的《他们很幸福》。心里想的是自己要不要来一场说走就走的旅行,做一个背包客、流浪的歌手、行吟诗人,赶着音乐...
    杨一说阅读 284评论 2 4
  • 最近参加了一个【二十一天体质改善课程】,要缘起于自己肠胃的不适,晚上睡觉都是胀胀的感觉,看到这个课程便毫不犹豫的报...
    粉饰依然阅读 339评论 0 2
  • 你好奇之下,同意了对方的视频邀请。 打开摄像头的瞬间,你就后悔了,视频里面是一个诡异的婴儿,看不清它的五官。 它对...
    邢老师说阅读 212评论 0 0
  • 零基础日语专业学生学习不到二年可以达到专业四级水平,同样情况下的留学生学汉语呢?可以达到hsk几级? 外教教日语大...
    YIMOSKY阅读 164评论 0 0