Moya + ObjectMapper + RxSwift
使用此三剑客,访问网络更加优雅。
Moya
Moya基于Alamofire的更高层网络请求封装抽象层.使用Moya访问网络有以下优点:
1.编译时检查正确的API端点访问.
2.使你定义不同端点枚举值对应相应的用途更加明晰.
3.提高测试地位从而使单元测试更加容易.
ObjectMapper
ObjectMapper是一个基于Swift语言开发的能够让 JSON 与 Object 之间轻易转换的类库。通过ObjectMapper我们可以将 JSON 数据转换成 Model 对象或将 Model 对象转换成 JSON 数据。
RxSwift
RxSwift是Swift函数响应式编程的一个开源库,由Github的ReactiveX组织开发,维护。
RxSwift的目的是让让数据/事件流和异步任务能够更方便的序列化处理,能够使用swift进行响应式编程。
在这里我强烈建议学习下RxSwift,尤其在使用MVVM模式下,可以实现双向绑定,下面推荐一些学习RxSwift的学习资源。
小菁的视频
CocoaPods
使用Moya + ObjectMapper + RxSwift最好使用CocoaPods进行集成,可以方便的管理项目里面的第三方库。关于如何使用CocoaPods,请google。
使用CocoaPods将下列第三方库进行集成。
platform :ios,'8.0'
use_frameworks!
target'YzzSwift'do
inherit! :search_paths
inhibit_all_warnings!
pod'RxSwift'
pod'RxCocoa'
pod'Moya'
pod'Moya/RxSwift'
pod'ObjectMapper'
ObjectMapper
首先建立一个AccountModel.swift
,导入ObjectMapper
,实现Mappable
import ObjectMapper
struct AccountModel: Mappable {
var accountID:String!
var avatarIcon:String?
var nickName:String?
var tk:String!
var userType:String!
init?(map: Map) {
}
mutating func mapping(map: Map) {
accountID <- map["accountID"]
avatarIcon <- map["avatarIcon"]
nickName <- map["nickName"]
tk <- map["tk"]
userType <- map["userType"]
}
}
在类中是无需标注mutating
关键字的,mutating
只针对值类型,如枚举,结构体 )
RequestManger
新建RequestManger.swift
import Alamofire
import Moya
class RequestManger {
class CustomServerTrustPoliceManager : ServerTrustPolicyManager {
override func serverTrustPolicy(forHost host: String) -> ServerTrustPolicy? {
return .disableEvaluation
}
public init() {
super.init(policies: [:])
}
}
class func defaultAlamofireManager() -> Manager {
let configuration = URLSessionConfiguration.default
configuration.httpAdditionalHeaders = Alamofire.SessionManager.defaultHTTPHeaders
let manager = Alamofire.SessionManager(configuration: configuration,serverTrustPolicyManager: CustomServerTrustPoliceManager())
manager.startRequestsImmediately = false
return manager
}
class func endpointMapping<Target: TargetType>(target: Target) -> Endpoint<Target> {
return MoyaProvider.defaultEndpointMapping(for: target)
}
}
注意:默认是Alamfire
,我这里是自己定制。主要是处理关于ssl的问题,具体可以点击关于对manger
如何添加受信任的白名单。EndpointClosure
可以对请求参数做进一步的修改,如可以修改endpointByAddingParameters endpointByAddingHTTPHeaderFields等
RequestPlugin
Moya中默认提供的3个插件
- NetworkLoggerPlugin
- NetworkActivityPlugin
- HTTP Authentication
import Moya
import Result
import SVProgressHUD
class RequestPlugin: PluginType {
let tk = "jKle07aY0pbSaP8ACk8_ZktK-0ivmFUTIDoLnDBw_FOSCw=="
let accountID = "1000024"
let tz = "Asia/Shanghai"
func prepare(_ request: URLRequest, target: TargetType) -> URLRequest {
var request = request
request.addValue(tk, forHTTPHeaderField: "tk")
request.addValue(accountID, forHTTPHeaderField: "accountID")
request.addValue(tz, forHTTPHeaderField: "tz")
return request
}
func willSend(_ request: RequestType, target: TargetType) {
print("*****************************************************************************\n请求连接:\(target.baseURL)\(target.path) \n方法:\(target.method)\n参数:\(String(describing: target.parameters)) ")
SVProgressHUD.show()
}
func didReceive(_ result: Result<Response, MoyaError>, target: TargetType) {
SVProgressHUD.dismiss()
let result = result
switch result {
case .failure(let moyaError):
switch moyaError {
case .underlying(let error) :
let errorNs = error as NSError
print(errorNs.code)
break
default:
break
}
break
default:
break
}
}
func process(_ result: Result<Response, MoyaError>, target: TargetType) -> Result<Response, MoyaError> {
return result
}
}
注意:这里可以在prepare
中添加request
请求头,比如一些网络访问的token
.
同时可以在此插件中添加网络访问指示器,willSend
中启动指示器,didReceive
中关闭指示器,process
方法主要是用于对访问网络的结果进行修改再返回。另外关于MoyaError
的错误我们该如何解析,需要使用到switch
来先解析MoyaError
,然后再解析underlying
,将解析出来的Error
转换成NSError
,最后输入错误结果。
NetworkLoggerPlugin
可以用来进行日志的管理,NetworkActivityPlugin
可以在此插件中处理状态栏中的小菊花.
TargetType
新建VesyncApi.swift
import Moya
import Alamofire
enum VesyncApi{
case login(account: String, password: String)
case deviceList()
}
extension VesyncApi:TargetType {
var baseURL: URL {
return URL(string: "https://192.168.100.60:5005")!
}
var path: String {
switch self {
case .login(_, _):
return "/vold/user/login"
case .deviceList():
return "/vold/user/devices"
}
}
var method: Moya.Method {
switch self {
case .login(_, _):
return .post
case .deviceList:
return .get
}
}
var parameters: [String : Any]? {
switch self {
case let .login(account, password):
return ["account": account, "password": password]
default :
return nil
}
}
public var parameterEncoding: ParameterEncoding {
return JSONEncoding.default
}
var sampleData: Data {
// switch self {
// case .login(_, _):
return "".data(using: String.Encoding.utf8)!
// }
}
var task: Task {
// switch self {
// case .login(_, _):
return .request
// }
}
}
RxSwift和ObjectMapper的中间件
新建Observable+ObjectMapper.swift
import Foundation
import RxSwift
import Moya
import ObjectMapper
import Result
extension Observable {
func mapObject<T: Mappable>(type: T.Type) -> Observable<T> {
return self.map { response in
//if response is a dictionary, then use ObjectMapper to map the dictionary
//if not throw an error
guard let dict = response as? [String: Any] else {
throw RxSwiftMoyaError.ParseJSONError
}
if let error = self.parseError(response: dict) {
throw error
}
return Mapper<T>().map(JSON: dict)!
}
}
func mapArray<T: Mappable>(type: T.Type) -> Observable<[T]> {
return self.map { response in
guard let dict = response as? [[String: Any]] else {
throw RxSwiftMoyaError.ParseJSONError
}
if let error = self.parseError(response: dict) {
throw error
}
return Mapper<T>().mapArray(JSONArray: dicts)
}
}
func parseServerError() -> Observable {
return self.map { (response) in
let name = type(of: response)
print(name)
guard let dict = response as? [String: Any] else {
throw RxSwiftMoyaError.ParseJSONError
}
if let error = self.parseError(response: dict) {
throw error
}
return self as! Element
}
}
fileprivate func parseError(response: [String: Any]?) -> NSError? {
var error: NSError?
if let value = response {
var code:Int?
var msg:String?
if let errorDic = value["error"] as? [String:Any]{
code = errorDic["code"] as? Int
msg = errorDic["msg"] as? String
error = NSError(domain: "Network", code: code!, userInfo: [NSLocalizedDescriptionKey: msg ?? ""])
}
}
return error
}
}
enum RxSwiftMoyaError: String {
case ParseJSONError
case OtherError
}
extension RxSwiftMoyaError: Swift.Error {}
provider
访问网络前我们需要先实例化一个provider
let provider = RxMoyaProvider<VesyncApi>(endpointClosure: RequestManger.endpointMapping,manager:RequestManger.defaultAlamofireManager(),plugins:[RequestPlugin()])
RxSwift
和ObjectMapper
配合使用访问
provider.request(.login(account: account, password: password))
.mapJSON().mapObject(type: AccountModel.self).subscribe(onNext: { (model) in
print(model.accountID)
}, onError: { (error) in
let e = error as NSError
print(e.code)
}).addDisposableTo(disposeBag)
注意:如果是object
我们则使用mapObject
,Array
则使用mapArray
参考文章:
http://www.jianshu.com/p/a728d03db236
最后希望大家对此文章做出指点,好改正不足。谢谢