前段时间,公司项目重构,决定采用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> {
return XYJMoyaHttp().sendRequest().request(.getBannar(parameters: paramaters)).mapObject(XYJResultList.self)
}
我们看下XYJMoyaHttp
参数:
EndpointClosure:可以对请求参数做进一步的修改,如可以修改endpointByAddingParameters endpointByAddingHTTPHeaderFields等
RequestClosure:你可以在发送请求前,做点手脚.判断有无网络做气泡提示 ,修改超时时间,打印一些数据等
StubClosure:可以设置请求的延迟时间,可以当做模拟慢速网络
Manager:请求网络请求的方式。默认是Alamofire
[PluginType] :Moya提供了一个插件机制,使我们可以建立自己的插件类来做一些额外的事情。比如写Log,显示“菊花”等。抽离出Plugin层的目的,就是把和自己网络无关的行为抽离。避免各种业务揉在一起不利于扩展
public class XYJMoyaHttp {
func sendRequest() -> RxMoyaProvider {
return RxMoyaProvider.init(endpointClosure: endpointClosure ,requestClosure: requestClosure,stubClosure: stubClosure,plugins: [NetworkLoggerPlugin.init(verbose: true,responseDataFormatter: {JSONResponseDataFormatter($0)}),spinerPlugin,XYJMoyaResponseNetPlugin()])
}
func sendUploadMultipart() -> RxMoyaProvider {
return RxMoyaProvider.init(endpointClosure: endpointClosure ,requestClosure: requestClosure ,plugins: [NetworkLoggerPlugin.init(verbose: true,responseDataFormatter: {JSONResponseDataFormatter($0)}),spinerPlugin,XYJMoyaResponseNetPlugin()])
}
// MARK: - 设置请求头部信息
let endpointClosure = { (target: T) -> Endpoint in
let url = target.baseURL.appendingPathComponent(target.path).absoluteString
let endpoint = Endpoint(
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, 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, target: TargetType) -> Result {
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(_ 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().map(JSONObject:json) else {
throw MoyaError.jsonMapping(self)
}
return object
}
// 这个主要是将JSON解析成多个Model并返回一个数组,不同的json格式写法不相同
public func mapArray(_ 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().mapArray(JSONArray: jsonDic) else {
throw MoyaError.jsonMapping(self)
}
return objects
}
}
extension ObservableType where E == Response {
// 这个是将JSON解析为Observable类型的Model
public func mapObject(_ type: T.Type) -> Observable {
return flatMap { response -> Observable in
return Observable.just(try response.mapObject(T.self))
}
}
// 这个是将JSON解析为Observable类型的[Model]
public func mapArray(_ 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: 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/