iOS开发实用技术

Moya + ObjectMapper + RxSwift

2017-07-27  本文已影响1151人  yunil

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

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
最后希望大家对此文章做出指点,好改正不足。谢谢

上一篇下一篇

猜你喜欢

热点阅读