Moya + ObjectMapper + RxSwift

使用此三剑客,访问网络更加优雅。

Moya

Moya基于Alamofire的更高层网络请求封装抽象层.使用Moya访问网络有以下优点:
1.编译时检查正确的API端点访问.
2.使你定义不同端点枚举值对应相应的用途更加明晰.
3.提高测试地位从而使单元测试更加容易.

ObjectMapper

ObjectMapper是一个基于Swift语言开发的能够让 JSON 与 Object 之间轻易转换的类库。通过ObjectMapper我们可以将 JSON 数据转换成 Model 对象或将 Model 对象转换成 JSON 数据。

RxSwift

RxSwiftSwift函数响应式编程的一个开源库,由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个插件

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()])

RxSwiftObjectMapper配合使用访问

        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我们则使用mapObjectArray则使用mapArray
参考文章:
http://www.jianshu.com/p/a728d03db236
最后希望大家对此文章做出指点,好改正不足。谢谢

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

推荐阅读更多精彩内容