Moya 初始化源码如下:
public init(endpointClosure: @escaping EndpointClosure = MoyaProvider.defaultEndpointMapping,
requestClosure: @escaping RequestClosure = MoyaProvider.defaultRequestMapping,
stubClosure: @escaping StubClosure = MoyaProvider.neverStub,
manager: Manager = MoyaProvider<Target>.defaultAlamofireManager(),
plugins: [PluginType] = [],
trackInflights: Bool = false) {
self.endpointClosure = endpointClosure
self.requestClosure = requestClosure
self.stubClosure = stubClosure
self.manager = manager
self.plugins = plugins
self.trackInflights = trackInflights
}
下面一一解析各参数:
endpointClosure
endpointClosure
默认的实现如下:
public final class func defaultEndpointMapping(for target: Target) -> Endpoint<Target> {
let url = target.baseURL.appendingPathComponent(target.path).absoluteString
return Endpoint(url: url,
sampleResponseClosure: { .networkResponse(200, target.sampleData) },
method: target.method,
parameters: target.parameters)
}
从默认实现中看出,他用于自定义一个 Endpoint
,而 Endpoint
用于定义请求头相关信息。其初始化源码如下:
public init(url: String,
sampleResponseClosure: @escaping SampleResponseClosure,
method: Moya.Method = Moya.Method.get,
parameters: [String: Any]? = nil,
parameterEncoding: Moya.ParameterEncoding = URLEncoding.default,
httpHeaderFields: [String: String]? = nil) {
self.url = url
self.sampleResponseClosure = sampleResponseClosure
self.method = method
self.parameters = parameters
self.parameterEncoding = parameterEncoding
self.httpHeaderFields = httpHeaderFields
}
从初始化方法看出,一般情况下我们只需要定制 httpHeaderFields
属性。
requestClosure
requestClosure
的默认实现如下:
public final class func defaultRequestMapping(for endpoint: Endpoint<Target>, closure: RequestResultClosure) {
if let urlRequest = endpoint.urlRequest {
closure(.success(urlRequest))
} else {
closure(.failure(Error.requestMapping(endpoint.url)))
}
}
从默认实现中看出,他用于定义当 Endpoint
有问题时,提前拦截请求,即决定是否执行请求或执行什么样的请求(我们可以回调一个修改了的 URLRequest
),也就是说这里可以定制 URLRequest
的属性,例如 timeoutInterval
、 cachePolicy
、 httpShouldHandleCookies
等,事实上这里也可以设置 httpHeaderFields
。
stubClosure
定义如下:
public typealias StubClosure = (Target) -> Moya.StubBehavior
即实现一个返回 Moya.StubBehavior
的闭包,这个 Moya.StubBehavior
用于表示是否/怎样去 stub
一个请求,说清楚点就是是否使用 SampleData 作为请求返回的数据,用于模拟网络请求。
public enum StubBehavior {
case never
case immediate
case delayed(seconds: TimeInterval)
}
manager
manager
默认实现为:
public final class func defaultAlamofireManager() -> Manager {
let configuration = URLSessionConfiguration.default
configuration.httpAdditionalHeaders = Manager.defaultHTTPHeaders
let manager = Manager(configuration: configuration)
manager.startRequestsImmediately = false
return manager
}
这里可以自定义 SessionManager
,一般用于定制 configuration
。configuration
包含了 requestCachePolicy
、 timeoutIntervalForRequest
、 httpAdditionalHeaders
、 httpCookieStorage
等设置。
注意
上述几个参数中,有一些相同设置被包含了,例如
httpHeaderFields
在endpoint
、URLRequest
、configuration
中均可设置。
plugins
PluginType
是一个 protocol
,实现以下方法分别可以:
-
在一个网络请求最终发送之前,可以做最后的修改;
func prepare(_ request: URLRequest, target: TargetType) -> URLRequest
-
在一个网络请求发送之前,可以立刻执行一些额外代码,例如显示 loading;
func willSend(_ request: RequestType, target: TargetType)
-
收到一个网络请求的响应后,在执行回调操作之前,可以执行一些额外代码;
func didReceive(_ result: Result<Moya.Response, Moya.Error>, target: TargetType)
-
修改返回的网络请求的响应。
func process(_ result: Result<Moya.Response, Moya.Error>, target: TargetType) -> Result<Moya.Response, Moya.Error>
如何初始化一个自定义的 MoyaProvider ?
根据以上的分析,我们可以根据自己的需要来初始化一个 MoyaProvider 了。下面是我在公司项目中创建 MoyaProvider 的代码:
// 设置请求头
let myEndpointClosure = { (target: Target) -> Endpoint<Target> in
let url1 = target.baseURL as URL
let url2 = url1.appendingPathComponent(target.path)
let endpoint: Endpoint<Target> = Endpoint<Target>(
url: url2.absoluteString,
sampleResponseClosure: {.networkResponse(200, target.sampleData)},
method: target.method,
parameters: target.parameters
)
return endpoint.adding(newHTTPHeaderFields: headerFields)// 请求头
}
// 设置超时时常等
let myRequestClosure = {(endpoint: Endpoint<Target>, closure: MoyaProvider<Target>.RequestResultClosure) -> Void in
if var urlRequest = endpoint.urlRequest {
urlRequest.timeoutInterval = 10// 超时时长 // TODO: 修改超时时长
closure(.success(urlRequest))
} else {
closure(.failure(MoyaError.requestMapping(endpoint.url)))
}
}
// 模拟网络请求
let myStubClosure: (Target) -> Moya.StubBehavior = { type in
switch type {
default:
return Moya.StubBehavior.delayed(seconds: 1)
}
}
// plugin:log、loading等
let myNetworkActivityPlugin = NetworkActivityPlugin { (type) in
switch type {
case .began:
NSLog("显示loading~~~~~~~~~~~~~~~~~~\n")
case .ended:
NSLog("隐藏loading~~~~~~~~~~~~~~~~~~\n")
}
}
//
let myNetworkLoggerPlugin = NetworkLoggerPlugin(verbose: true, responseDataFormatter: { (data: Data) -> Data in
// return Data()
do {
let dataAsJSON = try JSONSerialization.jsonObject(with: data)// Data 转 JSON
let prettyData = try JSONSerialization.data(withJSONObject: dataAsJSON, options: .prettyPrinted)// JSON 转 Data,格式化输出。
return prettyData
} catch {
return data
}
})
provider = MoyaProvider<Target>(endpointClosure: myEndpointClosure,
requestClosure: myRequestClosure,
stubClosure: myStubClosure,
plugins: [myNetworkActivityPlugin, myNetworkLoggerPlugin])
如何自由关闭和打开网络请求的Log日志?
显示Log日志在调试接口的时候还是很实用的,但是有时候我们只想看到某一个网络请求的日志,而隐藏其他的日志,如果每个网络请求的日志都混在一起找起来会很心累。有一个办法是在要调试某个接口之前,删除掉其他日志,我觉得这样做还是不够省事;所以我想到一个办法:
Debug时,自定义一个 UISwitch
控件,用于控制是否打印日志,是否把它放到 window 层保证一直都会显示。代码上,设置一个全局 Bool
类型参数 shouldShowLog
,根据 UISwitch
的开闭来改变它的值,然后根据这个参数来确定NetworkLoggerPlugin
是否打印日志。
下面是 NetworkLoggerPlugin
的代码,NetworkLoggerPlugin
的 output
属性负责打印哪些日志(包括了请求requestDataFormatter
和响应responseDataFormatter
的信息,查看源码得知即参数中的 items
)。:
func reversedPrint(_ separator: String, terminator: String, items: Any...) {
guard shouldShowLog else { return }
for item in items {
print(item, separator: separator, terminator: terminator)
}
}
let myNetworkLoggerPlugin = NetworkLoggerPlugin(verbose: true,
output: reversedPrint,
responseDataFormatter: { (data: Data) -> Data in
do {
let dataAsJSON = try JSONSerialization.jsonObject(with: data)
let prettyData = try JSONSerialization.data(withJSONObject: dataAsJSON, options: .prettyPrinted)
return prettyData
} catch {
return data
}
})
附一张自定义的 UISwitch
图片:
其他:Moya 版本更新
最近 Moya 升级版本后,一些关键代码发生了变化,例如在我们定义的 TargetType 中,增加了 var headers: [String : String]?
属性,用于设置请求头参数,相比以前的版本方便了不少;而之前 TargetType 中的 var parameters: [String: Any]?
被废除,但是我们可以在 var task: Task
属性中设置 parameters
参数。
var task: Task {
return Task.requestParameters(parameters: self.parameters, encoding: URLEncoding.default)
}
关于上传文件的网络操作,可以在 task
参数中返回 .uploadCompositeMultipart
这个枚举值。
var task: Task {
switch self {
case .uploadPhoto(let file):
let multipartFormData = MultipartFormData(provider: .data(file), name: "file", fileName: "someImage", mimeType: "multipart/form-data")
return .uploadCompositeMultipart([multipartFormData], urlParameters: ["token": token])
default:
return Task.requestParameters(parameters: self.parameters, encoding: URLEncoding.default)
}
}
有任何疑问欢迎留言。
网友评论
let data = MultipartFormData(provider: .data(Data()), name: "")
return .uploadCompositeMultipart([data], urlParameters: self.parameters!)