关于Moya
Moya是一个网络抽象层,它在底层将Alamofire进行封装,对外提供更简洁的接口供开发者调用。在以往的Objective-C中,大部分开发者会使用AFNetworking
进行网络请求,当业务复杂一些时,会对AFNetworking
进行二次封装,编写一个适用于自己项目的网络抽象层。在Objective-C中,有著名的YTKNetwork,它将AFNetworking
封装成抽象父类,根据不同的网络请求编写不同的子类,子类继承父类来实现请求业务。Moya
在项目层次中的地位,有点类似于YTKNetwork
。但是Moya
的设计思路和YTKNetwork
差距非常大。因为YTKNetwork
是比较经典的利用OOP思想(面向对象)设计的产物。而Moya
虽然也有使用到继承,但是它的整体上是以POP思想(Protocol Oriented Programming,面向协议编程)为主导的。
从上图可以看出App并不直接与Alamofire
进行交互,所有的网络请求都是通过Moya
发起的。官方给出Moya
的主要优点:
- 编译时检查
API endpoint
权限,检测是否使用正确。 - 通过
enum
枚举的关联值Target
、endpoints
定义清晰的用法。 - 测试
stubs
为一等数值,让单元测试变得更加简单。
Moya模块组成
由于Moya
是使用POP面向协议
来设计的一个网络抽象层,它整体的逻辑结构并没有明显的继承关系。Moya的核心代码,可以分成以下几个模块:
Moya类型组成
在使用Moya
进行网络请求时,需要进行配置来生成一个Request
,Request
的生成过程如下图:
我们根据上图,TargetType
是用来提供给开发者用于自定义各个接口的参数。
TargetType
TargetType
结构如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
public protocol TargetType {
/// The target's base `URL`.
/// 设置通用url
var baseURL: URL { get }
/// The path to be appended to `baseURL` to form the full `URL`.
/// path会追加到baseURL,构成完整的请求URL
var path: String { get }
/// The HTTP method used in the request.
/// HTTP请求类型设置,内部是Alamofire.HTTPMethod
/// 如.post .get .put
var method: Moya.Method { get }
/// Provides stub data for use in testing.
/// 提供测试所需要的所有数据
var sampleData: Data { get }
/// The type of HTTP task to be performed.
/// 所需要执行的HTTP任务
/// 如何发送/接收数据,如何添加数据,文件和数据流到请求的 body 中
var task: Task { get }
/// A Boolean value determining whether the embedded target performs Alamofire validation. Defaults to `false`.
/// 用于验证请求时返回的状态码的类型,默认.none
var validate: Bool { get }
/// The headers to be used in the request.
/// 用于设置请求的请求头
var headers: [String: String]? { get }
}
Endpoint
Endpoint
是由TargetType
演变而来的,它是Target
和URLRequest
的中间态,可以直接生成URLRequest
、设置HTTPHeader
和SampleResponseClosure,结构如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
open class Endpoint {
public typealias SampleResponseClosure = () -> EndpointSampleResponse
/// 请求URL,用于生成URLRequest的string
public let url: String
/// stubs测试时返回的数据 `EndpointSampleResponse`
public let sampleResponseClosure: SampleResponseClosure
/// 请求方式,内部其实是 Alamofire.HTTPMethod
/// 如.post .get .put
public let method: Moya.Method
/// 所需要执行HTTP请求任务
public let task: Task
/// 用于设置请求头headers
public let httpHeaderFields: [String: String]?
/// 初始化方法
public init(url: String,
sampleResponseClosure: @escaping SampleResponseClosure,
method: Moya.Method,
task: Task,
httpHeaderFields: [String: String]?) {
self.url = url
self.sampleResponseClosure = sampleResponseClosure
self.method = method
self.task = task
self.httpHeaderFields = httpHeaderFields
}
}
可以看出Endpoint
的属性基本对应TargetType
协议的get
方法,EndpointClosure
的作用主要是可以根据业务需求在这里重新定制网络请求,还可以通过 stub
进行数据测试,可以看看官方默认的闭包实现:
1
2
3
4
5
6
7
8
9
public final class func defaultEndpointMapping(for target: Target) -> Endpoint<Target> {
return Endpoint(
url: URL(target: target).absoluteString,
sampleResponseClosure: { .networkResponse(200, target.sampleData) },
method: target.method,
task: target.task,
httpHeaderFields: target.headers
)
}
MoyaProvider
MoyaProvider
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
open class MoyaProvider<Target: TargetType>: MoyaProviderType {
/// 用于传入的Target转化为Endpoint对象的闭包
public typealias EndpointClosure = (Target) -> Endpoint
/// 用于判断URLRequest是否需要执行和对URLRequest进行哪些设置的闭包
public typealias RequestResultClosure = (Result<URLRequest, MoyaError>) -> Void
/// 用于将Endpoint转化成真正的请求对象URLRequest闭包
public typealias RequestClosure = (Endpoint, @escaping RequestResultClosure) -> Void
/// 返回StubBehavior的枚举值
/// 用于判断Target返回数据和怎样使用stub返回数据
public typealias StubClosure = (Target) -> Moya.StubBehavior
public let endpointClosure: EndpointClosure
public let requestClosure: RequestClosure
public let stubClosure: StubClosure
/// Alamofire的session,用于发起请求
public let session: Session
/// 插件,通过插件机制可以做额外的事情,比如Log、数据加载...
public let plugins: [PluginType]
/// 是否过滤重复的请求,如果有重复的请求在处理中,就不会发起新的请求而是把 completion添加到对应的inflightCompletionBlocks中,根据Endpoint来判断是否为重复的请求
public let trackInflights: Bool
open internal(set) var inflightRequests: [Endpoint: [Moya.Completion]] = [:]
/// 与 Alamfire 的 callbackQueue 进行隔离,如果没有定义就会调用 Alamofire 默认的 queue ( main queue )
let callbackQueue: DispatchQueue?
let lock: NSRecursiveLock = NSRecursiveLock()
public init(endpointClosure: @escaping EndpointClosure = MoyaProvider.defaultEndpointMapping,
requestClosure: @escaping RequestClosure = MoyaProvider.defaultRequestMapping,
stubClosure: @escaping StubClosure = MoyaProvider.neverStub,
callbackQueue: DispatchQueue? = nil,
session: Session = MoyaProvider<Target>.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
}
}
MoyaProviderType
1
2
3
4
5
public protocol MoyaProviderType: AnyObject {
associatedtype Target: TargetType
func request(_ target: Target, callbackQueue: DispatchQueue?, progress: Moya.ProgressBlock?, completion: @escaping Moya.Completion) -> Cancellable
}
可以看出MoyaProvider
是支持MoyaProviderType
协议的的,主要作用是起到一个管理者的作用,负责串联各个模块。
defaultEndpointMapping
1
2
3
4
5
6
7
8
9
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
)
}
对Target
不做任何处理,直接返回Endpoint
.
defaultRequestMapping
1
2
3
4
5
6
7
8
9
10
11
12
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)))
}
}
defaultRequestMapping
主要作用是将Endpoint
转为RequestResultClosure
StubBehavior
1
2
3
4
5
6
7
8
9
10
11
public enum StubBehavior {
/// 不使用Stub返回数据.
case never
/// 立即使用Stub返回数据
case immediate
/// 一段时间间隔后使用Stub返回的数据.
case delayed(seconds: TimeInterval)
}
1
2
3
4
final class func neverStub(_: Target) -> Moya.StubBehavior {
/// 默认为不使用Stub返回数据
return .never
}
defaultAlamofireSession
1
2
3
4
5
6
final class func defaultAlamofireSession() -> Session {
let configuration = URLSessionConfiguration.default
configuration.headers = .default
return Session(configuration: configuration, startRequestsImmediately: false)
}
默认使用 URLSessionConfiguration.default
来生成 Session
, startRequestsImmediately
设置为false
,如果不设置为 false
, Alamofire 在创建 Request
后就会直接发起请求,在进行stub测试的情况下进行。但是在 Alamofire5 中这块逻辑已经做了调整,在创建 Request
时不会直接发起请求,只有在调用 .response
相关方法添加响应处理后才会发起请求。
PluginType
Moya
提供了一个插件协议、机制,主要用于请求发送或者接收时调用的,而且可以建立自己的插件类来做一些额外的事情,比如Log、“菊花”加载等。这里也使用协议对具体的对象类型进行抽象, MoyaProvider
不需要知道具体的类型是什么,只需要实现 PluginType
协议即可:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
public protocol PluginType {
/// 发送请求前调用,可以对URLRequest进行修改
func prepare(_ request: URLRequest, target: TargetType) -> URLRequest
/// 发送请求前最后调用的方法
func willSend(_ request: RequestType, target: TargetType)
/// 接收到响应结果时调用,会先调用该方法
/// 再调用MoyaProvider调用自己的completionHandler
func didReceive(_ result: Result<Moya.Response, MoyaError>, target: TargetType)
/// 响应结果的预处理器
func process(_ result: Result<Moya.Response, MoyaError>, target: TargetType) -> Result<Moya.Response, MoyaError>
}
PluginType
请求前由 Alamofire 的 RequestInterceptor
协议来实现。每次发起请求时, MoyaProvider
都会生成一个 ` MoyaRequestInterceptor` ,其实现如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
final class MoyaRequestInterceptor: RequestInterceptor {
private let lock: NSRecursiveLock = NSRecursiveLock()
var prepare: ((URLRequest) -> URLRequest)?
private var internalWillSend: ((URLRequest) -> Void)?
var willSend: ((URLRequest) -> Void)? {
get {
lock.lock(); defer { lock.unlock() }
return internalWillSend
}
set {
lock.lock(); defer { lock.unlock() }
internalWillSend = newValue
}
}
init(prepare: ((URLRequest) -> URLRequest)? = nil, willSend: ((URLRequest) -> Void)? = nil) {
self.prepare = prepare
self.willSend = willSend
}
func adapt(_ urlRequest: URLRequest, for session: Alamofire.Session, completion: @escaping (Result<URLRequest, Error>) -> Void) {
// 先调 prepare 对 urlRequest 进行处理,再调 willSend 。
let request = prepare?(urlRequest) ?? urlRequest
willSend?(request)
completion(.success(request))
}
}
/// 初始化
private func interceptor(target: Target) -> MoyaRequestInterceptor {
return MoyaRequestInterceptor(prepare: { [weak self] urlRequest in
return self?.plugins.reduce(urlRequest) { $1.prepare($0, target: target) } ?? urlRequest
})
}
Provider发送请求
官方文档里说明的Moya的基本使用步骤:
- 创建枚举,遵守TargetType协议,实现规定的属性。
- 初始化
provider = MoyaProvider<Myservice>()
- 调用provider.request,在闭包里处理请求结果。
请求前预处理
MoyaProvider
下面的接口来发起请求:
1
2
3
4
5
6
7
8
open func request(_ target: Target,
callbackQueue: DispatchQueue? = .none,
progress: ProgressBlock? = .none,
completion: @escaping Completion) -> Cancellable {
let callbackQueue = callbackQueue ?? self.callbackQueue
return requestNormal(target, callbackQueue: callbackQueue, progress: progress, completion: completion)
}
返回的结果使用了Cancellable
协议进行了封装,
1
2
3
4
5
6
public protocol Cancellable {
/// 判断请求是否已经取消
var isCancelled: Bool { get }
/// 取消请求
func cancel()
}
通过 Cancellable
的接口来进行相关调用,经过callbackqueue
的处理后会调用下面的方法。
1
2
3
4
5
6
7
8
9
10
func requestNormal(_ target: Target, callbackQueue: DispatchQueue?, progress: Moya.ProgressBlock?, completion: @escaping Moya.Completion) -> Cancellable {
let endpoint = self.endpoint(target)
let stubBehavior = self.stubClosure(target)
let cancellableToken = CancellableWrapper()
let pluginsWithCompletion: Moya.Completion = { result in
let processedResult = self.plugins.reduce(result) { $1.process($0, target: target) }
completion(processedResult)
}
}
- 1.
self.endpoint(target)
将target
转换为endpoint
1
2
3
open func endpoint(_ token: Target) -> Endpoint {
return endpointClosure(token)
}
2.self.stubClosure(target)
获取Target对应的Stub
3.生成一个
CancellableWrapper
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
internal class CancellableWrapper: Cancellable {
internal var innerCancellable: Cancellable = SimpleCancellable()
var isCancelled: Bool { return innerCancellable.isCancelled }
internal func cancel() {
innerCancellable.cancel()
}
}
internal class SimpleCancellable: Cancellable {
var isCancelled = false
func cancel() {
isCancelled = true
}
}
CancellableWrapper
使用一个SimpleCancellable
来实现Cancellable
协议,如果进行stub
测试,就直接使用SimpleCancellable
,如果发起实际请求,就会生成对应的 Cancellable
,替换 SimpleCancellable
。
- 4.使用
reduce
调用插件的process
方法对相应结果进行处理。
预处理后一些处理
请求前预处理完后,会通过trackInflights
判断是否需要进行处理重复请求。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
if trackInflights {
lock.lock()
/// 通过Endpoint获取对应的请求回调
var inflightCompletionBlocks = self.inflightRequests[endpoint]
/// 将pluginsWithCompletion追加到inflightCompletionBlocks
inflightCompletionBlocks?.append(pluginsWithCompletion)
self.inflightRequests[endpoint] = inflightCompletionBlocks
lock.unlock()
/// 判断inflightCompletionBlocks不为空时,表示有重复的请求在处理中,不需要发起新的请求,返回cancellableToken即可,否则设置对应的回调到inflightRequests中
if inflightCompletionBlocks != nil {
return cancellableToken
} else {
lock.lock()
self.inflightRequests[endpoint] = [pluginsWithCompletion]
lock.unlock()
}
}
设置一个performNetworking
闭包,这是执行请求的下一步,是在 endpoint → URLRequest 方法执行完成后的闭包。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
let performNetworking = { (requestResult: Result<URLRequest, MoyaError>) in
/// 判断请求是否被取消,如取消就不再发送请求
/// 返回错误类型为cancel的错误提示数据
if cancellableToken.isCancelled {
self.cancelCompletion(pluginsWithCompletion, target: target)
return
}
var request: URLRequest!
/// 判断请求结果是否为.success,如果不是则调用pluginsWithCompletion 处理对应的error
switch requestResult {
case .success(let urlRequest):
request = urlRequest
case .failure(let error):
pluginsWithCompletion(.failure(error))
return
}
/// 定义返回结果闭包,根据trackInflights调用不同的closure
let networkCompletion: Moya.Completion = { result in
if self.trackInflights {
self.inflightRequests[endpoint]?.forEach { $0(result) }
self.lock.lock()
self.inflightRequests.removeValue(forKey: endpoint)
self.lock.unlock()
} else {
/// 通过闭包通知所有插件,返回结果
pluginsWithCompletion(result)
}
}
/// 通过调用performRequest请求,替换cancellableToken中的innerCancellable,将所有参数继续传递
cancellableToken.innerCancellable = self.performRequest(target, request: request, callbackQueue: callbackQueue, progress: progress, completion: networkCompletion, endpoint: endpoint, stubBehavior: stubBehavior)
}
/// 通过requestClosure将Endpoint转换为URLRequest,转换完成后则调用 performNetworking
requestClosure(endpoint, performNetworking)
return cancellableToken
发起请求
performRequest
根据是否进行stub
测试调用不同的方法,根据 switch stubRequest
和 sendRequest 来分别执行对应的请求方式。
stubRequest
在进行stub
测试时不会发起真正的网络请求,而是通过Endpoint
获取对应的假数据进行回调:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
open func stubRequest(_ target: Target, request: URLRequest, callbackQueue: DispatchQueue?, completion: @escaping Moya.Completion, endpoint: Endpoint, stubBehavior: Moya.StubBehavior) -> CancellableToken {
let callbackQueue = callbackQueue ?? self.callbackQueue
/// 初始化一个CancellableToken,由于没有发起真正的请求,所以CancellableToken 没有包含对应的 Request
let cancellableToken = CancellableToken { }
/// 调用插件的相关方法
let preparedRequest = notifyPluginsOfImpendingStub(for: request, target: target)
let plugins = self.plugins
/// 通过createStubFunction进行验证由Endpoint的 sampleResponseClosure来获取对应的假数据,同时也会调用插件的对应方法
let stub: () -> Void = createStubFunction(cancellableToken, forTarget: target, withCompletion: completion, endpoint: endpoint, plugins: plugins, request: preparedRequest)
switch stubBehavior {
case .immediate:
switch callbackQueue {
case .none:
stub()
case .some(let callbackQueue):
callbackQueue.async(execute: stub)
}
case .delayed(let delay):
let killTimeOffset = Int64(CDouble(delay) * CDouble(NSEC_PER_SEC))
let killTime = DispatchTime.now() + Double(killTimeOffset) / Double(NSEC_PER_SEC)
(callbackQueue ?? DispatchQueue.main).asyncAfter(deadline: killTime) {
stub()
}
case .never:
fatalError("Method called to stub request when stubbing is disabled.")
}
return cancellableToken
}
sendRequest
1
2
3
4
5
6
7
8
9
10
11
func sendRequest(_ target: Target, request: URLRequest, callbackQueue: DispatchQueue?, progress: Moya.ProgressBlock?, completion: @escaping Moya.Completion) -> CancellableToken {
/// 通过target生成一个MoyaRequestInterceptor来调用插件对应的方法
let interceptor = self.interceptor(target: target)
let initialRequest = session.request(request, interceptor: interceptor)
setup(interceptor: interceptor, with: target, and: initialRequest)
let validationCodes = target.validationType.statusCodes
let alamoRequest = validationCodes.isEmpty ? initialRequest : initialRequest.validate(statusCode: validationCodes)
/// 调用Alamofire的方法发送网络请求
return sendAlamofireRequest(alamoRequest, target: target, callbackQueue: callbackQueue, progress: progress, completion: completion)
}
sendAlamofireRequest
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
func sendAlamofireRequest<T>(_ alamoRequest: T, target: Target, callbackQueue: DispatchQueue?, progress progressCompletion: Moya.ProgressBlock?, completion: @escaping Moya.Completion) -> CancellableToken where T: Requestable, T: Request {
/// 获取插件
let plugins = self.plugins
var progressAlamoRequest = alamoRequest
/// 生成对应的progressClosure
let progressClosure: (Progress) -> Void = { progress in
let sendProgress: () -> Void = {
progressCompletion?(ProgressResponse(progress: progress))
}
if let callbackQueue = callbackQueue {
callbackQueue.async(execute: sendProgress)
} else {
sendProgress()
}
}
/// 判断progressCompletion不为空开始发送网络请求
if progressCompletion != nil {
switch progressAlamoRequest {
case let downloadRequest as DownloadRequest:
if let downloadRequest = downloadRequest.downloadProgress(closure: progressClosure) as? T {
progressAlamoRequest = downloadRequest
}
case let uploadRequest as UploadRequest:
if let uploadRequest = uploadRequest.uploadProgress(closure: progressClosure) as? T {
progressAlamoRequest = uploadRequest
}
case let dataRequest as DataRequest:
if let dataRequest = dataRequest.downloadProgress(closure: progressClosure) as? T {
progressAlamoRequest = dataRequest
}
default: break
}
}
/// 生成 completionHandler ,这一步的目的是为了将 Alamofire 的 response 转换为 Moya 所需要的格式,调用插件的 didReceive 方法
let completionHandler: RequestableCompletion = { response, request, data, error in
let result = convertResponseToResult(response, request: request, data: data, error: error)
plugins.forEach { $0.didReceive(result, target: target) }
if let progressCompletion = progressCompletion {
let value = try? result.get()
switch progressAlamoRequest {
case let downloadRequest as DownloadRequest:
progressCompletion(ProgressResponse(progress: downloadRequest.downloadProgress, response: value))
case let uploadRequest as UploadRequest:
progressCompletion(ProgressResponse(progress: uploadRequest.uploadProgress, response: value))
case let dataRequest as DataRequest:
progressCompletion(ProgressResponse(progress: dataRequest.downloadProgress, response: value))
default:
progressCompletion(ProgressResponse(response: value))
}
}
completion(result)
}
progressAlamoRequest = progressAlamoRequest.response(callbackQueue: callbackQueue, completionHandler: completionHandler)
progressAlamoRequest.resume()
return CancellableToken(request: progressAlamoRequest)
}
上述请求流程基本是Moya
完整的网络请求流程,可以看到Moya
是在Alamofire
基础上提供了一层封装,而且是面向协议的一层封装,简单易用,同时也提供了足够灵活的插件和入口给开发者调用。
总结
Plugins 插件
Moya
提供比较完整的插件功能:
AccessTokenPlugin
管理AccessToken的插件CredentialsPlugin
管理认证的插件NetworkActivityPlugin
管理网络状态的插件NetworkLoggerPlugin
管理网络log的插件
协议编程
Moya
中使用比较多的协议来接口进行抽象,比如TargetType
、PluginType
、Cancellable
等,使用协议可以更容易对接口惊醒扩展和隐藏具体的类型,同时也可以通过extension
来提供默认实现。
1
2
3
4
5
6
public extension PluginType {
func prepare(_ request: URLRequest, target: TargetType) -> URLRequest { return request }
func willSend(_ request: RequestType, target: TargetType) { }
func didReceive(_ result: Result<Moya.Response, MoyaError>, target: TargetType) { }
func process(_ result: Result<Moya.Response, MoyaError>, target: TargetType) -> Result<Moya.Response, MoyaError> { return result }
}
枚举enum
使用
Moya
中使用enum
来实现不同的逻辑调用,这样更加安全易用。
1
2
3
4
5
6
7
8
9
10
11
public enum EndpointSampleResponse {
/// The network returned a response, including status code and data.
case networkResponse(Int, Data)
/// The network returned response which can be fully customized.
case response(HTTPURLResponse, Data)
/// The network failed to send the request, or failed to retrieve a response (eg a timeout).
case networkError(NSError)
}
Alamofire
桥接使用
Moya
是在Alamofire
基础上封装的网络请求框架,很多基本的基础类型都是Alamofire
提供,Moya
通过使用类型桥接隐藏Alamofire
。
1
2
3
4
5
6
7
8
public typealias Session = Alamofire.Session
public typealias Method = Alamofire.HTTPMethod
public typealias ParameterEncoding = Alamofire.ParameterEncoding
public typealias JSONEncoding = Alamofire.JSONEncoding
public typealias URLEncoding = Alamofire.URLEncoding
public typealias RequestMultipartFormData = Alamofire.MultipartFormData
public typealias DownloadDestination = Alamofire.DownloadRequest.Destination
public typealias RequestInterceptor = Alamofire.RequestIntercepto
Encodable
Moya
的Task
支持Encodable
进行编码,在 Endpoint
生成对应的 URLRequest
时会通过 Encodable
参数来设置 httpBody
1
2
3
extension Endpoint {
case let .requestJSONEncodable(encodable):
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
internal extension URLRequest {
mutating func encoded(encodable: Encodable, encoder: JSONEncoder = JSONEncoder()) throws -> URLRequest {
do {
let encodable = AnyEncodable(encodable)
httpBody = try encoder.encode(encodable)
let contentTypeHeaderName = "Content-Type"
if value(forHTTPHeaderField: contentTypeHeaderName) == nil {
setValue("application/json", forHTTPHeaderField: contentTypeHeaderName)
}
return self
} catch {
throw MoyaError.encodableMapping(error)
}
}
}
这里需要声明方法为 mutating
,因为方法会修改属性。可以看到方法会通过 encodable
参数生成一个 AnyEncodable struct
:
1
2
3
4
5
6
7
8
9
10
11
12
struct AnyEncodable: Encodable {
private let encodable: Encodable
public init(_ encodable: Encodable) {
self.encodable = encodable
}
func encode(to encoder: Encoder) throws {
try encodable.encode(to: encoder)
}
}
通过 AnyEncodable
可以把传进行来的协议参数转换成具体的值参数,而这个参数是遵循 Encodable
协议的。这是在 Swift
上进行类型擦除的一种方式,把具体的类型隐藏起来。为什么要进行类型擦除呢?因为 JSONEncoder
的 encode
方法只接收具体的类型参数,不接受协议参数:
1
open func encode<T>(_ value: T) throws -> Data where T : Encodable
MultiTarget
Moya
在MoyaProvider
使用时需要指定TargetType
的类型,而 App 中可能会根据 baseURL
分成多个 TargetType
,这样导致我们需要根据不同的 TargetType
提供不同的 MoyaProvider
,Moya
提供了MultiTarget
来解决只用一个MoyaProvider
来支持所有的TargetType
。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
enum MultiTarget: TargetType {
case target(TargetType)
public init(_ target: TargetType) {
self = MultiTarget.target(target)
}
public var path: String {
return target.path
}
public var baseURL: URL {
return target.baseURL
}
public var method: Moya.Method {
return target.method
}
public var sampleData: Data {
return target.sampleData
}
public var task: Task {
return target.task
}
public var validationType: ValidationType {
return target.validationType
}
public var headers: [String: String]? {
return target.headers
}
public var target: TargetType {
switch self {
case .target(let target): return target
}
}
}
定义一个使 multiple target
的provider
:
1
let provider = MoyaProvider<MultiTarget>()
RxSwift
Moya
可以跟RxSwift
响应式框架进行交互.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
extension Reactive where Base: MoyaProviderType {
func request(_ token: Base.Target, callbackQueue: DispatchQueue? = nil) -> Single<Response> {
return Single.create { [weak base] single in
let cancellableToken = base?.request(token, callbackQueue: callbackQueue, progress: nil) { result in
switch result {
case let .success(response):
single(.success(response))
case let .failure(error):
single(.error(error))
}
}
return Disposables.create {
cancellableToken?.cancel()
}
}
}
}
普通请求返回的是 Single
, Single
是Observable
的另外一个版本,不可以发出多个元素,只能发出一个元素或者一个error
事件,这和网络请求的流程一致,每个请求只能返回一个响应结果。
带有进度的请求如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
func requestWithProgress(_ token: Base.Target, callbackQueue: DispatchQueue? = nil) -> Observable<ProgressResponse> {
let progressBlock: (AnyObserver) -> (ProgressResponse) -> Void = { observer in
return { progress in
observer.onNext(progress)
}
}
let response: Observable<ProgressResponse> = Observable.create { [weak base] observer in
let cancellableToken = base?.request(token, callbackQueue: callbackQueue, progress: progressBlock(observer)) { result in
switch result {
case .success:
observer.onCompleted()
case let .failure(error):
observer.onError(error)
}
}
return Disposables.create {
cancellableToken?.cancel()
}
}
// Accumulate all progress and combine them when the result comes
return response.scan(ProgressResponse()) { last, progress in
let progressObject = progress.progressObject ?? last.progressObject
let response = progress.response ?? last.response
return ProgressResponse(progress: progressObject, response: response)
}
}
这里使用Observable
,因为在请求过程中会调用onNext
更新进度。使用scan
操作符判断是否需要使用之前的response
。
ReactiveCocoa
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
func request(_ token: Base.Target, callbackQueue: DispatchQueue? = nil) -> SignalProducer<Response, MoyaError> {
return SignalProducer { [weak base] observer, lifetime in
let cancellableToken = base?.request(token, callbackQueue: callbackQueue, progress: nil) { result in
switch result {
case let .success(response):
observer.send(value: response)
observer.sendCompleted()
case let .failure(error):
observer.send(error: error)
}
}
lifetime.observeEnded {
cancellableToken?.cancel()
}
}
}
由于ReactiveCocoa
没有类似RxSwift
那样的Single
,所以对于普通请求,会在 send(value: Value)
之后立即调用sendComplete()
。
其他
Moya也为我们提供了很多Observable的扩展,让我们能更轻松的处理MoyaResponse,常用的如下:
filter(statusCodes:)
过滤response状态码filterSuccessfulStatusCodes()
过滤状态码为请求成功的mapJSON()
将请求response转化为JSON格式mapString()
将请求response转化为String格式
1
2
3
4
5
6
7
8
9
10
11
12
func filter<R: RangeExpression>(statusCodes: R) throws -> Response where R.Bound == Int {
guard statusCodes.contains(statusCode) else {
throw MoyaError.statusCode(self)
}
return self
}
func filter(statusCode: Int) throws -> Response {
return try filter(statusCodes: statusCode...statusCode)
}
func filterSuccessfulStatusCodes() throws -> Response {
return try filter(statusCodes: 200...299)
}
1
2
3
4
5
6
7
8
9
10
func mapJSON(failsOnEmptyData: Bool = true) throws -> Any {
do {
return try JSONSerialization.jsonObject(with: data, options: .allowFragments)
} catch {
if data.count < 1 && !failsOnEmptyData {
return NSNull()
}
throw MoyaError.jsonMapping(self)
}
}
开源社区
Extensions
- Moya-ObjectMapper - Moya的ObjectMapper封装,易于JSON序列化
- Moya-SwiftyJSONMapper - Moya的SwiftyJSON 封装,易于JSON序列化
- Moya-Argo - Moya 的Argo 封装,易于JSON序列化
- Moya-ModelMapper - Moya 的ModelMapper 封装,易于JSON序列化
- Moya-Gloss - Moya 的Gloss 封装,易于JSON序列化
- Moya-JASON - Moya 的JASON 封装,易于JSON序列化
- Moya-JASONMapper - Moya 的JASON封装,易于JSON序列化
- Moya-Unbox - Moya 的Unbox 封装,易于JSON序列化
- MoyaSugar – Moya语法糖
- Moya-EVReflection - Moya 的EVReflection 封装,易于JSON序列化 (包括子项目 RxSwift 和 ReactiveCocoa)
- Moya-Marshal - Moya 的Marshal 封装,易于JSON序列化
- Moya-Decodable - Decodable bindings for Moya for easier JSON serialization
- SwiftMoyaCodeGenerator - Extension for Paw Mac App to generate Moya files based in rest documentation.
库
- NetClient/Moya - 基于Swift3的通用的HTTP网络库。
应用
- Drrrible - Dribbble for iOS using ReactorKit
- Eidolon - The Artsy Auction Kiosk App
- Insights for Instagram - A simple iOS Instagram’s media insights App written in Swift 4
- Papr - An Unsplash app for iOS.
- SwiftHub - Reactive Github iOS client written in RxSwift and MVVM.
- GitTime - GitHub iOS client using ReactorKit and Moya.