Moya使用以及处理服务器端不同格式的数据


1、集成Moya到项目中

① CocoaPods

pod 'Moya', '~> 12.0'

# or 
# 如果你的项目中用到了RxSwift就可以导入这个
pod 'Moya/RxSwift', '~> 12.0'

# or
# 如果你的项目中用到了Reactive就可以导入这个
pod 'Moya/ReactiveSwift', '~> 12.0'

② Carhage

github "Moya/Moya"

​ 然后根据使用情况把Framework导入到项目中

还有两种方式,一个是Swift Package manager,一个是手动安装,这个可以参考这里




2、快速上手

① 建立一个enum

import Moya

enum UserAPI {
    case login(username: String, password: String)
}

② 继承TargetType协议

重写以下属性

extension UserAPI: TargetType {
    // 基础地址 例如: https://www.baidu.com/
    var baseURL: URL
    // 剩下的地址 例如: user
    var path: String
    // 请求方式: get post put delete ...
    var method: Method
    // 这个是用来测试返回的data,可以在Test中进行测试
    var sampleData: Data
    // 请求
    var task: Task
    // 请求头
    var headers: [String : String]?
}

③ 新建一个Provider

let userProvider = MoyaProvider()

做完上面的事情,就可以愉快的使用了




3、使用

一般使用方法:

let userProvider = MoyaProvider()
userProvider.request(.login(username: "xxx", password: "xxxx"))
{ (res) in
    // TODO: .....
}
RxMoya
let userProvider = MoyaProvider()
userProvider.rx.request(.login(username: "xxx", password: "xxx"))
            .subscribe(
                onSuccess: { res in 
                            .... } , 
                onError: { error in 
                          .... }
            ).disposed(by: bag)

​ 到这里就可以愉快的使用Moya进行网络请求了,现在我们的网络请求中一般都有返回值,已登录为例,服务器都会反馈信息,判断是否成登录,App在做处理。

​ 假设返回有以下几种情况

  • 登录成功

    {
        "token": "xxxxxxxx",
        "expire_at": "xxxxx"
    }
    
  • 登录失败

    {
        "error_msg": "username or password invalid",
        "error_code": -1
    }
    
  • 网络请求失败 request time out

在处理返回值之前,我们先了解一下MoyaMoya为我们提供了几个方法让我们可以快速的把返回值处理成为我们需要的数据,也提供了一下常用方法,这里我们简单聊下:

过滤:

filter(statusCodes: R) throws -> Response where R.Bound == Int 过滤反馈的状态码范围

func filter(statusCode: Int) throws -> Response 过滤反馈的状态码

filterSuccessfulStatusCodes过滤全部成功的状态码 200...299

filterSuccessfulStatusAndRedirectCodes 过滤 200...399

形变:

mapImage 转化成Image

mapJSON 转化为JSON

mapString 获取某个值转化为String,根据传入的keyPath

map 转化为某个遵从Decodable协议的对象

了解了这几种常用的方法之后,我们就可以改动一下请求方法了,

首先定义一个结构体:

struct LoginSuccess: Decodable {
    let token: String
    let expire_at: String
}

然后修改一下请求方法

let userProvider = MoyaProvider()
userProvider.rx
.map(LoginSuccess.self)
.request(.login(username: "xxx", password: "xxx"))
            .subscribe(
                onSuccess: { loginRes in 
                      print(loginRes)// 这里就可以直接拿到对象了
                            } , 
                onError: { error in 
                          .... }
            ).disposed(by: bag)

上面的写法很舒服,直到你遇见了账号密码输入错的情况,这个时候服务器端返回值就不一样了,此时会报错Json解析错误,虽然也不会有什么问题,但是这个时候,你要拿到里面的错误信息和错误代码,就不好拿了。所以我们就需要给Moya动动刀子了




4、扩展RxMoya,更好的处理网络请求

PS: 该内容文字略多,可能略多废话,同时也会涉及到一点Moya和RxSwift的源码部分,没兴趣或者没耐心的大佬们可以关闭网页了。

​ 既然要处理,那么我们的目标就是可以这样子调用。

let userProvider = MoyaProvider()
userProvider.rx
.map(LoginSuccess.self)
.request(.login(username: "xxx", password: "xxx"))
            .subscribe(
                onSuccess: { loginRes in 
                      print(loginRes)// 这里就可以直接拿到对象了
                            } , 
                onErrorInfo: { errorInfo in 
                    print(errorInfo)
                },
                onError: { error in 
                          .... }
            ).disposed(by: bag)

在上面我们知道,当我们网络请求成功,但是是登陆失败的时候,服务器端返回的数据格式完全不同。

这个时候还是用我们之前定义的LoginSuccess结构体去解析,就会解析失败,而我们希望做到的是,当登陆失败的时候,不要直接告诉我们解析失败了,而是把这些信息解析成为另外一个对象类型,然后发送出来。

要想改动他原来的东西,首先你得知道他的原理和处理方式。

所以这里我们需要查看一下Moya的源码了,看看他的map方法里面究竟干了什么,不然我们怎么知道怎么处理呢?

文件名称:Single+Response.swift

    public func map(_ type: D.Type, atKeyPath keyPath: String? = nil, using decoder: JSONDecoder = JSONDecoder(), failsOnEmptyData: Bool = true) -> Single {
        return flatMap { .just(try $0.map(type, atKeyPath: keyPath, using: decoder, failsOnEmptyData: failsOnEmptyData)) }
    }

可以看到这里调用了RxSwiftflatMap方法,返回一个Observable对象(Single是一个特殊的Observable,其只能发送一次事件或者Error),其类型就是我们传入的类型。

更核心一点的解码工作调用的是文件名称:Response.swift

func map(_ type: D.Type, atKeyPath keyPath: String? = nil, using decoder: JSONDecoder = JSONDecoder(), failsOnEmptyData: Bool = true) throws -> D { .... }

这里有点长,就不粘贴上来了,只要知道这个方法里面是把返回值转化为对象,如果有某一步失败了,就调用throw抛出异常。

看完上面两个方法,就可以发现我们有两个地方可以介入:

一个是在Single+Response.swift中的map方法,

一个是在Response.swiftmap方法。

这里我选择的是第一个地方,理由就是在RxSwfitflatMap是需要返回一个Observable的,我可以在这里处理返回给外界的Observable到底是什么。

先再回顾一下登录失败的反馈示例

{
    "error_msg": "username or password invalid",
    "error_code": -1
}

既然想用JSONDecoder去解码,我们可能就会定义如下的类或者结构体

struct ErrorInfo: Decodable, Error {
    let error_msg: String
    let error_code: Int
}

有了这个struct我们就可以在map中搞事情了。

1、新建文件RxSwift+Moya.swift(文件名随意,我这里用的是这个)

然后导入框架

import Moya
import RxSwift 
2、为PrimitiveSequence扩展方法

这里我们为PrimitiveSequence扩展方法,使用where添加约束,然后我们依照之前的map方法,自己实现一个,通过上面的文章知道,flatMap里面我们是可以自己处理一些东西的。

所以我们可以在返回之前看下能不能解析成为我们自定义的ErrorInfo结构体,如果可以,我们就通过Single.error发送出去.

这里大家可能会有疑问,为什么是通过Single.error发送,因为原方法的返回值是Single,这里不能直接把类型改了,所以就使用了这种方式。


extension PrimitiveSequence where TraitType == SingleTrait, ElementType == Response  {
   
    public func mapDiy(_ type: D.Type, atKeyPath keyPath: String? = nil, using decoder: JSONDecoder = JSONDecoder(), failsOnEmptyData: Bool = true) -> Single<(D)> {
        return flatMap {
            if let error = try? $0.map(ErrorInfo.self, atKeyPath: keyPath, using: decoder, failsOnEmptyData: failsOnEmptyData) {
                return Single.error(error)
            }
            
        return .just(try $0.map(type, atKeyPath: keyPath, using: decoder, failsOnEmptyData: failsOnEmptyData))
        }
    }
}

上面把处理好的信息,通过Single.error出去啦,接下来就是要捕获他了,到这里我们就需要进入RxSwift的源码一探究竟

文件名称:Single.swift

...

public func subscribe(onSuccess: ((ElementType) -> Void)? = nil, onError: ((Swift.Error) -> Void)? = nil) -> Disposable {
        #if DEBUG
             let callStack = Hooks.recordCallStackOnError ? Thread.callStackSymbols : []
        #else
            let callStack = [String]()
        #endif

        return self.primitiveSequence.subscribe { event in
            switch event {
            case .success(let element):
                onSuccess?(element)
            case .error(let error):
                if let onError = onError {
                    onError(error)
                } else {
                    Hooks.defaultErrorHandler(callStack, error)
                }
            }
        }
    }

...

上面的代码我们大部分都不用去关注,只需要看switch中代码即可,这里我们看到有两个case,一个是成功一个是失败。

大家应该还记得上面我们把ErrorInfo通过error的形式发送出来了。所以这里我们只需要在.error的时候,把error转化为ErrorInfo,如果转化成功了,就表示是登录失败了,我们就可以把这个值单独发出去了,而不是通过onError

所以我们就在扩展一个方法

extension PrimitiveSequenceType where Self.TraitType == RxSwift.SingleTrait {

    public func subscribeGiz(onSuccess: ((Self.ElementType) -> Void)? = nil, onErrorInfo: ((ErrorInfo) -> Void)? = nil, onError: ((Error) -> Void)? = nil) -> Disposable {
        #if DEBUG
        let callStack = Hooks.recordCallStackOnError ? Thread.callStackSymbols : []
        #else
        let callStack = [String]()
        #endif

        return self.primitiveSequence.subscribe { event in
            
            switch event {
            case .success(let element):
                onSuccess?(element)
            case .error(let error):
                /// 这里是获取的关键地方
                if let errorInfo = error as? ErrorInfo { 
                    onErrorInfo?(errorInfo)
                }
                else if let onError = onError {
                    onError(error)
                } else {
                    Hooks.defaultErrorHandler(callStack, error)
                }
            }
        }
    }

}

到了这里,我们就对Moya框架进行了一轮手术,这里我们就可以做到刚开始设定的调用方式了,更加优雅的截取了一些信息。

总结:

如果你的服务器端反馈的数据也类似于这种模式,成功和失败采用的是两种不同的结构,同时你需要根据不同的error_code做不同的处理,希望这篇文章可以帮到你。如果有不同意见和处理方式的欢迎指正,讨论。

-------------- 如果转载请注明地址 ------------------

你可能感兴趣的:(Moya使用以及处理服务器端不同格式的数据)