Moya+ RxSwift+HandyJSON 优雅处理网络请求

前言

在移动端的开发中,网络请求是必不可少的。之前写过Alamofire的简单使用,但是一般开发中都会对这些第三库封装,然后使用,之前自己封装的demo也是借鉴了一些Moya的设计思路。今天就介绍一下Moya一个帮助你处理网络层的第三方框架。

介绍

Moya

Moya是一个帮助我们管理Alamofire的网络管理层,可以让我们去更清晰的去管理我们的网络请求。

Moya的版本 VS Swift版本

Moya+ RxSwift+HandyJSON 优雅处理网络请求_第1张图片

集成方式

Moya本身支持多种集成,本文使用CocoaPods集成。Moya本身就支持了对RxSwift的扩展,所以我们只需要在Podfile里添加一下:

pod 'Moya', '~> 10.0'

# or 

pod 'Moya/RxSwift', '~> 10.0'

然后执行 pod install
在使用的文件中,引入即可:import Moya

简单使用

定义枚举,存储网络请求

// 1 定义一个枚举,存放我们的网络请求
enum ApiManager {
    case login(username:String,password:String,token:String)

}

实现moya的TargetType协议

extension ApiManager:TargetType{

    var baseURL: URL {
        return URL.init(string: BaseURL)!
    }

    //请求路径
    var path:String{

        switch self {
        case .login(username: _, password:_ , token:_):
            return "login/accountLogin"

        }

    }

    var headers: [String: String]? {
        return nil;
    }
    //请求的参数
    var parameters: [String: Any]? {
        switch self {
        case .login(username: let userName, password: let pwd, token: let token):
            return ["account":userName,"pwd":pwd,"deviceToken":token];

        }

    }

    ///请求方式
    var method: Moya.Method {
        switch self {
        case .login(username: _, password: _, token: _):
        return .post;

        }
    }

    /// The method used for parameter encoding.
    var parameterEncoding: ParameterEncoding {
        return URLEncoding.default
    }

    /// Provides stub data for use in testing.
    var sampleData: Data {
        return "".data(using: String.Encoding.utf8)!
    }

    //MARK:task type
    var task: Task {
       return .requestPlain
    }

    var validate: Bool {
        return false
    }

}

发起网络请求

    let provider = MoyaProvider();

     provider.request(.login(username: "haha", password: "123456", token: "qwe")) { (result) in
            if result.error == nil{
                LLog(result.value);
            }
        }

RxSwift

RxSwift是一个可以帮助我们简化异步编程的框架,类似于OC的RxCocoa、Java的RXJava、JS的rxjs、kotlin的RxKotlin等,属于Rx家族的一员。学习曲线,相对陡峭。适用于MVVM框架,身边的开发者,对其也是褒贬不一。个人感觉还是一个值得开发者去学习、使用的框架。这边文章就不详细介绍,具体的可移步查看RXSwift的中文文档。

HandyJSON

阿里开源的一个数据序列化的框架,功能类似于OC的MJExtension.个人感觉在Swift中HandyJSON和SwiftyJSON都是比较好用的数据处理框架。在这里就不做过多的介绍,具体的使用您可以查看HandyJSON的中文文档

正题(Moya+ RxSwift+HandyJSON 处理网络请求)

封装网络请求类

参见上文的Moya简繁使用

数据解析

在这里我们使用HanyJSON处理接口返回数据,因为我们使用RxSwift,在处理网络请求,所有我们是对ObservableType 数据类型 扩展mapModel。

extension ObservableType where E == Response {
    public func mapModel<T: HandyJSON>(_ type: T.Type) -> Observable<T> {
        return flatMap { response -> Observable<T> in
            return Observable.just(response.mapModel(T.self))
        }
    }
}
extension Response {
    func mapModel<T: HandyJSON>(_ type: T.Type) -> T {
        let jsonString = String.init(data: data, encoding: .utf8)
        return JSONDeserializer<T>.deserializeFrom(json: jsonString)!
    }
}

调用示例

使用provider异步请求,并处理返回数据。

// 1、接口请求完成后, subscribe 会收到next和complete两个事件 当event.element不为空时即为next事件。
        provider.rx.request(.login(username: "151xxxxxxxxxx", password: "123456", token: "")).asObservable().mapModel(LoginResponseModel .self).subscribe { (event) in
             //当event.element 不为空时,返回的就是我们之前mapModel的类
            if let model = event.element{
                 LLog(model.data.accessToken);
                 LLog(model.data.crowdToken);
            }else{
                LLog("当前事件:\(event)")
            }
            }.disposed(by: disposeBag);


//直接处理next事件,error和completed分开处理
 provider.rx.request(.login(username: "15136176473", password: "123456", token: "")).asObservable().mapModel(LoginResponseModel .self).subscribe(onNext: { (model) in
            LLog(model.data.crowdToken)
        }, onError: { (error) in
            LLog("请求出错")
        }, onCompleted: nil, onDisposed: nil).disposed(by: disposeBag);

扩展

  public init(endpointClosure: @escaping EndpointClosure = MoyaProvider.defaultEndpointMapping,
                requestClosure: @escaping RequestClosure = MoyaProvider.defaultRequestMapping,
                stubClosure: @escaping StubClosure = MoyaProvider.neverStub,
                callbackQueue: DispatchQueue? = nil,
                manager: Manager = MoyaProvider.defaultAlamofireManager(),
                plugins: [PluginType] = [],
                trackInflights: Bool = false) {}

Moya是一个比较全面的好用的网络管理工具,在上面我们只是使用了部分功能。通过provider的初始化,我们可以发现在开发中我们还可以处理很多。比如:

插件机制

Moya 的插件机制也很好用,提供了以下接口:

    /// Called to modify a request before sending
    func prepare(_ request: URLRequest, target: TargetType) -> URLRequest

    /// Called immediately before a request is sent over the network (or stubbed).
    func willSend(_ request: RequestType, target: TargetType)

    /// Called after a response has been received, but before the MoyaProvider has invoked its completion handler.
    func didReceive(_ result: Result, target: TargetType)

    /// Called to modify a result before completion
    func process(_ result: Result, target: TargetType) -> Result

与主功能接口脱离,降低了耦合度我们可以再这里处理请求的权限、token、loading等。 例如:

public final class RequestLoadingPlugin:PluginType{

    public func willSend(_ request: RequestType, target: TargetType) {
        LLog("接口开始请求")
    self.showHUD()
    }

    public func didReceive(_ result: Result, target: TargetType) {
        LLog("接口请求完成")
        self.dismissHUD();
    }
    //MARK:-是否显示请求加载框
    fileprivate  func  showHUD(_ isShow:Bool = true){
        if(isShow){
            SVProgressHUD.show();
        }

    }

    //MARK:-隐藏请求加载框
    fileprivate  func dismissHUD(){
        SVProgressHUD.dismiss();

    }
}

在provider的初始化注册我们初始化的插件即可

  let provider = MoyaProvider(plugins:[RequestLoadingPlugin()]);

请求处理机制

  public typealias EndpointClosure = (Target) -> Endpoint
  public typealias RequestClosure = (Endpoint, @escaping RequestResultClosure) -> Void
  public typealias StubClosure = (Target) -> Moya.StubBehavior
   public typealias RequestResultClosure = (Result) -> Void

从上面的三个闭包中,我们可以看出

  • EndpointClosure 是输入一个Target 返回一个Endpoint 也就是完成Target->Endpoint。

    Endpoint的数据结构

open class Endpoint {
    public typealias SampleResponseClosure = () -> EndpointSampleResponse

    open let url: String
    open let sampleResponseClosure: SampleResponseClosure
    open let method: Moya.Method
    open let task: Task
    open let httpHeaderFields: [String: String]?
    }

初始化的默认值是:

    public final class func defaultEndpointMapping(for target: Target) -> Endpoint {
        return Endpoint(
            url: URL(target: target).absoluteString,
            sampleResponseClosure: { .networkResponse(200, target.sampleData) },
            method: target.method,
            task: target.task,
            httpHeaderFields: target.headers
        )
    }

上面的代码只是创建并返回Endpoint实例。很多时候,我们可以自定义这个闭包来做更多额外的事情。比如我们处理一些网络状态码、或者结合stub 模拟网络数据等。
- RequestClosure 是输入一个Endpoint和RequestResultClosure闭包 返回Void
RequestClosure主要是实现Endpoint->NSURLRequest 用于发起真正的网络请求。

 public typealias RequestResultClosure = (Result) -> Void

在输入中RequestResultClosure就是对接口请求完成后的返回。
初始化的默认值是:

    public final class func defaultRequestMapping(for endpoint: Endpoint, closure: RequestResultClosure) {
        do {
            let urlRequest = try endpoint.urlRequest()
            closure(.success(urlRequest))
        } catch MoyaError.requestMapping(let url) {
            closure(.failure(MoyaError.requestMapping(url)))
        } catch MoyaError.parameterEncoding(let error) {
            closure(.failure(MoyaError.parameterEncoding(error)))
        } catch {
            closure(.failure(MoyaError.underlying(error, nil)))
        }
    }

这里就是处理最后的urlRequest。如果你想设置全局的urlRequest,这里也是最后的机会了。我们可以再这里修改请求的超时时间、缓存策略、cookie等。
示例:

    let requestClosure = { (endpoint: Endpoint, closure: (Result) -> Void)  -> Void in
        do {
            var  urlRequest = try endpoint.urlRequest()
            urlRequest.timeoutInterval = 30.0
            urlRequest.cachePolicy = .reloadIgnoringLocalAndRemoteCacheData
            urlRequest.httpShouldHandleCookies = false
            closure(.success(urlRequest))
        } catch MoyaError.requestMapping(let url) {
            closure(.failure(MoyaError.requestMapping(url)))
        } catch MoyaError.parameterEncoding(let error) {
            closure(.failure(MoyaError.parameterEncoding(error)))
        } catch {
            closure(.failure(MoyaError.underlying(error, nil)))
        }

    }

示例

    let provider = MoyaProvider(requestClosure:requestClosure,plugins:[RequestLoadingPlugin()]);
  • StubClosure 是输入一个Target 返回一个StubBehavior的闭包。
    默认是:neverStub
 public typealias StubClosure = (Target) -> Moya.StubBehavior
public enum StubBehavior {

    /// 不使用Stub来返回模拟的网络数据   
     case never

    /// 立刻返回Stub的数据
    case immediate

    /// 在几秒后返回stub的数据
    case delayed(seconds: TimeInterval)
}

示例:

编写测试数据

extension ApiManager:TargetType{
  ...

    /// Provides stub data for use in testing.
    var sampleData: Data {
        switch self {
        case .login(username: _, password: _, token: _):
            return "{'code': 200,'Token':'123455'}".data(using: String.Encoding.utf8)!
        default:
            return "".data(using: String.Encoding.utf8)!
        }
    }
}

初始化EndpointClosure

let endPointAction = { (target: ApiManager) -> Endpoint in
    let url = target.baseURL.appendingPathComponent(target.path).absoluteString

    switch target {
    case .login(username: _, password:_ , token:_):
        return  Endpoint(
            url: url,
            sampleResponseClosure: { .networkResponse(200, target.sampleData) },
            method: target.method,
            task: target.task,
            httpHeaderFields: target.headers
        )}
}

初始化StubClosure

let stubAction: (_ type:ApiManager) -> Moya.StubBehavior = { type in
    switch type {
    case .login(username: _, password:_ , token:_):
        return Moya.StubBehavior.delayed(seconds: 3.0)
    }
}

初始化provider

   let provider = MoyaProvider<ApiManager>(endpointClosure:endPointAction,requestClosure:_requestClosure,stubClosure:stubAction,plugins:[RequestLoadingPlugin()]);

Manager

Moya本身并不是直接处理网络请求的第三方库。它只是一个抽象的网络层,对真正发起网络请求的Manager进行管理:例如Alamofire
默认参数:

  public final class func defaultAlamofireManager() -> Manager {
        let configuration = URLSessionConfiguration.default
        configuration.httpAdditionalHeaders = Manager.defaultHTTPHeaders

        let manager = Manager(configuration: configuration)
        manager.startRequestsImmediately = false
        return manager
    }

封装Alamofire,使Moya.Manager == Alamofire.SessionManager

public typealias Manager = Alamofire.SessionManager
internal typealias Request = Alamofire.Request
internal typealias DownloadRequest = Alamofire.DownloadRequest
internal typealias UploadRequest = Alamofire.UploadRequest
internal typealias DataRequest = Alamofire.DataRequest

所以,当我们想要定义自己的Manager,我们也可以传入自己的Manager到Moya的provider中。
示例

let policies: [String: ServerTrustPolicy] = [
    "example.com": .PinPublicKeys(
        publicKeys: ServerTrustPolicy.publicKeysInBundle(),
        validateCertificateChain: true,
        validateHost: true
    )
]
let policies: [String: ServerTrustPolicy] = [
    "example.com": .pinPublicKeys(
        publicKeys: ServerTrustPolicy.publicKeys(),
        validateCertificateChain: true,
        validateHost: true
    )
]

let manager:Manager = Manager(configuration: URLSessionConfiguration.default, delegate: SessionDelegate(), serverTrustPolicyManager: ServerTrustPolicyManager(policies: policies))
let provider = MoyaProvider(manager: manager)

以上,基本覆盖了Moya开发中API使用示例以及代码,总的来说Moya还是一个很不错的网络管理工具。

你可能感兴趣的:(第三方库,Swift)