如图是Moya/14.0 版本的项目结构,在14.0之前Moya使用了自定义的Result包,此版本改为使用了原生的Result包,除此之外还引用了Alamofire 这个网络请求库,这篇文章我们将聚焦于Moya本身,简单分析分析Moay的项目源码。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 provider = MoyaProvider<GitHub>() provider.request(.zen) { result in switch result { case let .success(moyaResponse): let data = moyaResponse.data let statusCode = moyaResponse.statusCode // do something with the response data or statusCode case let .failure(error): // this means there was a network failure - either the request // wasn't sent (connectivity), or no response was received (server // timed out). If the server responds with a 4xx or 5xx error, that // will be sent as a ".success"-ful response. } }
一个简单的请求如上,它由一个provider 发起,入参为GitHub.zen 一个遵守了TargetType 协议的枚举。以这两条线开始,我们回到项目源码展开分析。
Target&Task 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 public protocol TargetType { /// The target's base `URL`. var baseURL: URL { get } /// The path to be appended to `baseURL` to form the full `URL`. var path: String { get } /// The HTTP method used in the request. var method: Moya.Method { get } /// Provides stub data for use in testing. var sampleData: Data { get } /// The type of HTTP task to be performed. var task: Task { get } /// The type of validation to perform on the request. Default is `.none`. var validationType: ValidationType { get } /// The headers to be used in the request. var headers: [String: String]? { get } }
TargetType 定义了MoyaProvider 所必要的一些字段,也就是一个请求的基本入参。 完美提现了依赖倒置原则 ,后续的使用有点依赖注入的内味儿
Task 顾名思义即任务,Task定义了请求数据的传输方式以及编码方式,比如
1 2 3 4 5 6 7 8 9 /// A requests body set with encoded parameters. case requestParameters(parameters: [String: Any], encoding: ParameterEncoding) /// A "multipart/form-data" upload task. case uploadMultipart([MultipartFormData]) /// A file download task to a destination. case downloadDestination(DownloadDestination)
MoyaProvider 一个请求的发起者provider ,是一个遵守了MoyaProviderType 协议的类
1 2 3 4 5 6 7 8 9 public protocol MoyaProviderType: AnyObject { associatedtype Target: TargetType /// Designated request-making method. Returns a `Cancellable` token to cancel the request later. func request(_ target: Target, callbackQueue: DispatchQueue?, progress: Moya.ProgressBlock?, completion: @escaping Moya.Completion) -> Cancellable } open class MoyaProvider<Target: TargetType>: MoyaProviderType
它的request方法定义了一个请求的几个基本元素
Target - 一个请求所需要的基本信息
callbackQueue - 回调所在的队列, 默认是主队类(由下层框架Alamofire决定)
progress - 请求进度的回调
completion - 请求完成的回调
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 /// 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<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 }
###MoyaProvider 的初始化方法,除了定义一些上面request方法的入参之外,还包含了以下一些属性配置
endpointClosure
requestClosure
stubClosure
session
plugins
trackInflights
endpointClosure 1 2 /// Closure that defines the endpoints for the provider. public typealias EndpointClosure = (Target) -> Endpoint
这个个闭包将会在每次请求时被执行. 它负责返回一个 Endpoint 实例对象,用来配置Moya.
大多数情况下, 这个闭包仅仅直接从target,method和task转化为一个 Endpoint 实例对象. 它将在每次request时被执行, 你可以做任何你想要的. 比如, 你可以在里面测试网络错误,如超时、设置header. 以下是Moya提供的默认实现
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 ) }
我们来看下Endpoint 这个对象里做了什么,最主要的就是下面这个方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 public func urlRequest() throws -> URLRequest { guard let requestURL = Foundation.URL(string: url) else { throw MoyaError.requestMapping(url) } var request = URLRequest(url: requestURL) request.httpMethod = method.rawValue request.allHTTPHeaderFields = httpHeaderFields switch task { case .requestPlain, .uploadFile, .uploadMultipart, .downloadDestination: return request case .requestData(let data): request.httpBody = data return request ..... }
这个方法主要做了以下几件事
1.Init URLRequest
2.设置request的url、method、header
3.根据不同的Task设置request的body
可以说一个请求的几个关键参数都在在这里设置的,此外Endpoint 还添加了一些添加header、替换task、支持hash等方法
requestClosure 1 2 3 4 5 /// Closure that decides if and what request should be performed. public typealias RequestResultClosure = (Result<URLRequest, MoyaError>) -> Void /// Closure that resolves an `Endpoint` into a `RequestResult`. public typealias RequestClosure = (Endpoint, @escaping RequestResultClosure) -> Void
这个closure主要作用是通过endpoint获取到URLRequest 对象(有异常则继续对外抛出异常),我们可以在发送请求前在此对当前这个urlRequest 做一些修改动作,比如设置timeoutInterval(超时时间)等,以下是默认的defaultRequestMapping
1 2 3 4 5 6 7 8 9 10 11 12 13 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))) } }
stubClosure 1 2 /// Closure that decides if/how a request should be stubbed. public typealias StubClosure = (Target) -> Moya.StubBehavior
这个closure主要是用来配置是否以及如何使用mock的响应数据,一般用于单元测试时使用
session Alamofire中定义的 Session 对象,Moya只负责请求的组织,具体如何请求的交给底层请求库,默认使用的是Alamofire
plugins 1 2 3 4 5 6 7 8 9 10 11 12 13 public protocol PluginType { /// 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<Moya.Response, MoyaError>, target: TargetType) /// Called to modify a result before completion. func process(_ result: Result<Moya.Response, MoyaError>, target: TargetType) -> Result<Moya.Response, MoyaError> }
插件的方法在请求之前和之后会被调用(类似于hook了一个request的生命周期),我们可以在这边做很多事,比如在prepare中修改request,添加一个HUDPlugin,在请求发起和结束后显示HUD,添加一个LogPlugin,打印request以及response等等。
trackInflights 表示是跟踪正在发送的请求, 如果为true, 正在发送的请求将被加入inflightRequests这个字典里, 默认为false
1 2 3 public let trackInflights: Bool open internal(set) var inflightRequests: [Endpoint: [Moya.Completion]] = [:]
以上就是MoyaProvider 类中的主要内容,当然其中的很多对象的定义都放到了单独的类(符合单一原则),比如
Endpoint.swift
MoyaProvider+Defaults.swift(定义defaultRequestMapping/defaultEndpointMapping等)
Plugin.swift
Moya+Alamofire.swift(关联Alamofire,主要是一些命名空间的重定义和功能的转接)
RequestTypeWrapper(RequestType包装器)
ValidationType(状态码过滤)
对外的request 方法并没有展示多少细节,相关的实现都放在了internal的requestNormal 中, 我们来看看这个方法的具体实现细节
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 62 63 64 65 66 func requestNormal(_ target: Target, callbackQueue: DispatchQueue?, progress: Moya.ProgressBlock?, completion: @escaping Moya.Completion) -> Cancellable { // 通过target + endpointClosure 初始化 获得 endpoint对象 let endpoint = self.endpoint(target) // 通过target + stubClosure 初始化 获得 stubBehavior对象 let stubBehavior = self.stubClosure(target) 获取request cancellableToken, 用来后续cancel request let cancellableToken = CancellableWrapper() // 遍历plugin,在completion之前 process response let pluginsWithCompletion: Moya.Completion = { result in let processedResult = self.plugins.reduce(result) { $1.process($0, target: target) } completion(processedResult) } //如果支持追踪请求,那就讲请求以endpoint: [completion]的形式存入字典中, 并返回cancel token if trackInflights { lock.lock() var inflightCompletionBlocks = self.inflightRequests[endpoint] inflightCompletionBlocks?.append(pluginsWithCompletion) self.inflightRequests[endpoint] = inflightCompletionBlocks lock.unlock() if inflightCompletionBlocks != nil { return cancellableToken } else { lock.lock() self.inflightRequests[endpoint] = [pluginsWithCompletion] lock.unlock() } } // 定义 RequestResultClosure let performNetworking = { (requestResult: Result<URLRequest, MoyaError>) in //如果请求已经取消,走cancel completion if cancellableToken.isCancelled { self.cancelCompletion(pluginsWithCompletion, target: target) return } var request: URLRequest! switch requestResult { case .success(let urlRequest): request = urlRequest case .failure(let error): pluginsWithCompletion(.failure(error)) return } 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根据不同Task send request,plugin的遍历、body的设置,statusCodes的过滤等等一些操作都在后续的流程中 cancellableToken.innerCancellable = self.performRequest(target, request: request, callbackQueue: callbackQueue, progress: progress, completion: networkCompletion, endpoint: endpoint, stubBehavior: stubBehavior) } requestClosure(endpoint, performNetworking) return cancellableToken }
上面我们提到stubClosure 时提到Moya提供了stub的功能,它是使用单独的请求方法,以做stub时的一些处理操作,比如针对plugin和cancel的处理
1 open func stubRequest(_ target: Target, request: URLRequest, callbackQueue: DispatchQueue?, completion: @escaping Moya.Completion, endpoint: Endpoint, stubBehavior: Moya.StubBehavior) -> CancellableToken {//do some thing}
总结 总的来说,Moya+Alamofire 的组合有点类似于Android中的Retrofit+OKHttp ,Moya在这里扮演的是一个网络抽象层 的角色,就源码来说并不复杂,其中设计的思路值得学习学习。