关于Moya的官方可参考: 点击查看
Moya官方使用下图来对比直接使用Alamofire和用Moya的区别(左:Alamofire,右:Moya)
Moya包含模块:
Moya流程图:
Moya使用
在项目中可通过Pod,Carthage等方式引入Moya
CocoaPods:
pod 'Moya','~> 14.0'
# or
pod 'Moya/RxSwift' , '~> 14.0'
# or
pod 'Moya/ReactiveSwift' , '~> 14.0'
Carthage:
github "Moya/Moya" -> 14.0
Moya使用介绍:
Targets
使用Moya,首先需要定义一个target(通常是继承TargetType协议的枚举变量),接下来,只需要处理这些targets(即:希望调用API完成的操作)
Targets必须继承TargetType
TargetType协议要求在枚举中定义一个baseURL属性。注意:baseURL的值不会取决于self的值,而是返回一个固定值(如果有多个API baseURL,需要使用多个枚举和Moya providers)
例如:
public enum GitHub {
case zen
case userProfile(String)
case userRepositories(String)
}
extension GitHub: TargetType {
public var baseURL: URL { return URL(string: "https://api.github.com")! }
//path可以用来拼接相对路径:(使用了String的扩展方法urlEscaped)
public var path: String {
switch self{
case .zen:
return "/zen"
case .userProfile(let name):
return "/users/\(name.urlEscaped)"
case .userRepositories(let name):
return "/users/\(name.urlEscaped)/repos"
}
}
//设置method,这里使用的是GET方法;如果请求需要POST或别的方法,可以通过switch self来返回合适的值
public var method: Moya.Method {
return .get
}
//请求任务事件(这里附带上参数)
public var task:Task{
switch self{
case .userRepositories:
return .requestParameters(parameters: ["sort":"pushed"], encoding: URLEncoding.default)
default:
return .requestPlain
}
}
//是否执行Alamofire验证
public var validationType : ValidationType{
switch self{
case .zen:
return .successCodes
default:
return .none
}
}
//sampleData属性,这是TargetType协议所必须的。任何想要调用target必须提供一些非空的NSData类型的返回值。
//就是做单元测试模拟的数据,只会在单元测试文件中有作用
public var sampleData:Data{
switch self{
case .zen:
return "Half measures are as bad as nothing at all.".data(using: String.Encoding.utf8)!
case .userProfile(letname):
return "{\"login\": \"\(name)\", \"id\": 100}".data(using: String.Encoding.utf8)!
case .userRepositories(let name):
return "[{\"name\": \"\(name)\"}]".data(using: String.Encoding.utf8)!
}
}
//指定headers,可通过switch self来返回不同的header
public var headers: [String:String]? {
return nil
}
}
做完上面的TargetType之后,构造Provider就很简单啦
Provider
provider是网络请求的提供者,所有的网络请求都通过provider来调用。
通过枚举来指定要访问的具体API
provider最简单的创建方法:
let provider = MoyaProvider<GitHub>() //GitHub就是遵循TargetType协议的枚举
通过Moya源码可知MoyaProvider是一个实现了MoyaProviderType协议的公开类,需要传入一个遵循TargetType协议的对象名,这是泛型的常规用法
open class MoyaProvider<Target: TargetType>: MoyaProviderType {
......
}
简单配置后就可以使用
provier.request(.zen){ result in
//......
}
request方法返回一个Cancellable,它有一个可以取消request的公共的方法。
MoyaProvider的构造方法如下:
/// Initializes a provider.
public init(endpointClosure: @escaping EndpointClosure = MoyaProvider.defaultEndpointMapping,
requestClosure:@escaping RequestClosure = MoyaProvider.defaultRequestMapping,
stubClosure:@escaping StubClosure = MoyaProvider.neverStub,
callbackQueue:DispatchQueue? =nil,
session:Session= MoyaProvider.defaultAlamofireSession(),
plugins: [PluginType] = [],
trackInflights:Bool=false) {
self.endpointClosure= endpointClosure
self.requestClosure= requestClosure
self.stubClosure= stubClosure
self.session = session
self.plugins= plugins
self.trackInflights= trackInflights
self.callbackQueue= callbackQueue
}
了解Moya的高级用法,需要先了解清晰MoyaProvider构造方法的所有参数
1、endpointClosure 一个endpoints闭包,它可以将target转换成具体的EndPoint实例
let endpointClosure: MoyaProvider<HTTPBin>.EndpointClosure = { target in
let task:Task
switch target.task {
case let .uploadMultipart(multipartFormData):
let additional = Moya.MultipartFormData(provider: .data("test2".data(using: .utf8)!), name:"test2")
var newMultipartFormData = multipartFormData
newMultipartFormData.append(additional)
task = .uploadMultipart(newMultipartFormData)
default:
task = target.task
}
return Endpoint(url: URL(target: target).absoluteString, sampleResponseClosure: {.networkResponse(200, target.sampleData)}, method: target.method, task: task, httpHeaderFields: target.headers)
}
let provider = MoyaProvider(endpointClosure:endpointClosure )
这样初始化MoyaProvider的时候,不需要在说明target的具体类型,Swit会根据endpointClosure推断。
MoyaProvider.defaultEndpointMapping 为默认实现
2、requestClosure
可以将Endpoint转换为NSURLRequest
let requestClosure = {(endpoint:Endpoint,done:MoyaProvider.RequestResultClosure) in
do{
var request=try endpoint.urlRequest()
done(.success(request))
}catch{
done(.failure(MoyaError.underlying(error)))
}
}
let provider= MoyaProvider(requestClosure:requestClosure)
3、stubClosure
它返回一个.Never(默认),或.Immediate,或.Delayed(seconds),可以延迟这个stub模拟请求(具体n秒)。例如:.Delayed(1),会把每个请求延迟1秒
这样的好处就是当需要模拟不同于其他的特殊请求是,可以编写自己的闭包:
let provider = MoyaProvider<MyTarget>(stubClosure: { target: MyTarget -> Moya.StubBehavior in
switch target {
/* Return something different based on the target. */ }
})
4、callbackQueue
可指定callback的Queue
5、session
可针对URLSessionConfiguration进行配置
final class func defaultAlamofireSession() ->Session{
let configuration = URLSessionConfiguration.default
configuration.headers = .default
returnSession(configuration: configuration, startRequestsImmediately:false)
}
6、plugins
插件数组,它们在发起请求之前和收到返回之后调用,比如:开始网络请求之前的NetworkActivityPlugin, 结束网络之后的日志NetworkLoggerPlugin
使用方式:
static var plugins:[PluginType{
let activityPlugin = NewNetworkActivityPlugin{(state,targetType) in
switch state{
case .began:
//显示loading
case .ended:
//关闭loading
}
}
return [activityPlugin,myLoggorPlugin]
}
let userModuleProvider = MoyaProvider<UserModule>(plugins:plugins)
7、trackInflights
是否要跟踪重复网络请求
MultiTarget
正常情况下,都是一个target对应一个Provider
有时候程序会根据业务逻辑拆分成多个target,这样target可能就会有多个,如果有多个target我们就创建多少个Provider,会让程序的逻辑复杂化。
特别是当它们使用同样的plugings或closures时,又要做一些额外的工作去维护。
那么借助MultiTarget,可以让多个target都使用相同的Provider
例子:
定义Target
public enum OPKeyConfigTarget {
case config
}
extension OPKeyConfigTarget: TargetType {
//服务器地址
public var baseURL: URL {
switch self{
case .config:
return URL(string: "http://xxx.com")!
}
}
//各个请求的具体路径
public var path: String {
switch self{
case .config:
return "/path/xxx"
}
}
//请求类型
public var method: Moya.Method {
return.post
}
//做单元测试模拟的数据,只会在单元测试文件中有作用
public var sampleData: Data {
return "{}".data(using: String.Encoding.utf8)!
}
//请求任务事件(这里附带上参数)
public var task: Task {
switch self{
case .config:
var params : [String:Any] = [:]
return .requestParameters(parameters: params, encoding:URLEncoding.default)
}
}
//请求头
public var headers: [String : String]? {
return nil
}
}
public enum OPMarketsTarget{
case market
}
extension OPMarketsTarget: TargetType{
//服务器地址
public var baseURL: URL {
return URL(string: "http://xxx.com")!
}
//各个请求的具体路径
public var path: String {
return "/path/xxx"
}
//请求类型
public var method: Moya.Method {
return .get
}
//做单元测试模拟的数据,只会在单元测试文件中有作用
public var sampleData: Data {
return "{}".data(using: String.Encoding.utf8)!
}
//请求任务事件(这里附带上参数)
public var task: Task {
return .requestPlain
}
//请求头
public var headers: [String : String]? {
return nil
}
}
Provider定义
1对1 使用方式
let keyConfigProvider = MoyaProvider<OPKeyConfigTarget>()
let marketsProvider = MoyaProvider<OPMarketsTarget>()
使用多个target的Provider可以如下方式定义
let provider = MoyaProvider<MultiTarget>()
Provider使用
一对一 使用方式
keyConfigProvider.request(.config) { result in
//...
}
marketsProvider.request(.market) { result in
//...
}
使用多个target的Provider,如下方式
provider.request(MultiTarget(OPKeyConfigTarget.config)) { result in
//...
}
provider.request(MultiTarget(OPMarketsTarget.market)) { result in
//...
}
Moya针对返回结果的处理:
Moya 会将 Alamofire 成功或失败的响应包裹在 Result 枚举中返回,具体值如下:
.success(Moya.Response):成功的情况。我们可以从 Moya.Response 中得到返回数据(data)和状态(status )
.failure(MoyaError):失败的情况。这里的失败指的是服务器没有收到请求(例如可达性/连接性错误)或者没有发送响应(例如请求超时)。我们可以在这里设置个延迟请求,过段时间重新发送请求。
provider.request(MultiTarget(OPKeyConfigTarget.config)) { result in
switch result{
case let .success(response):
//方式一:
let statusCode = response.statusCode //响应状态码
let data = response.data //响应数据
//方式二:过滤正确的状态码
do{
try response.filterSuccessfulStatusCodes()
let data =try response.mapJSON()
print("data===\(data)")
//继续做一些其他事情
}catch{
//处理错误状态码的响应
}
case let .failure(_):
//错误处理
break
}
}
针对错误的类型,可以通过switch语句判断具体的MoyaError错误类型:
case let .failure(error):
switch error {
case .imageMapping(let response):
print("错误原因:\(error.errorDescription ?? "")")
print(response)
case .jsonMapping(let response):
print("错误原因:\(error.errorDescription ?? "")")
print(response)
case .statusCode(let response):
print("错误原因:\(error.errorDescription ?? "")")
print(response)
case .stringMapping(let response):
print("错误原因:\(error.errorDescription ?? "")")
print(response)
case .underlying(let error1, let response):
print("错误原因:\(error.errorDescription ?? "")")
print(error1)
print(response as Any)
case .requestMapping:
print("错误原因:\(error.errorDescription ?? "")")
print("nil")
case .objectMapping(let error1, let response):
print(error1)
print("错误原因:\(error.errorDescription ?? "")")
print(response)
case .encodableMapping(let response):
print("错误原因:\(error.errorDescription ?? "")")
print(response)
case .parameterEncoding(let response):
print("错误原因:\(error.errorDescription ?? "")")
print(response)
}