Moya introduction
Moya is a set of network abstraction layer framework based on Alamofire.
Personally, I think Alamofire is based on how to call requests more conveniently in URLSession, while Moya is based on Alamofire to manage apis better by abstracting URLs, parameters, etc.
The basic template
Moya’s encapsulation of the API is based on enum, generating requests through different uses of enumeration for different endpoints.
enum GitHub {
case zen
case userProfile(String)}extension GitHub: TargetType {
var baseURL: URL { return URL(string: "https://api.github.com")! }
var path: String {
switch self {
case .zen:
return "/zen"
case .userProfile(let name):
return "/users/\(name)"}}var method: Moya.Method {
return .get
}
var task: Task {
return .requestPlain
}
var sampleData: Data {
switch self {
case .zen:
return "Half measures are as bad as nothing at all.".data(using: String.Encoding.utf8)!
case .userProfile(let name):
return "{\"login\": \"\(name)\", \"id\": 100}".data(using: String.Encoding.utf8)! }}var validationType: ValidationType {
return .successAndRedirectCodes
}
var headers: [String: String]? {
return nil}}Copy the code
Inherit TargetType by enumeration, adding a detailed implementation.
var provider = MoyaProvider<GitHub>()
provider.request(target) { response in
if case .failure(let error) = response {
receivedError = error
}
}
Copy the code
Finally, the generate generates the request based on the TargetType provider.
This is the basic implementation of Moya. It is too basic to repeat.
Codable
Codable is Apple’s protocol for parsing data into Models without using third-party libraries such as ObjectMapper and SwiftyJson.
Here’s a simple Codable example:
struct Demo: Codable {
var name: String
var age: Int
}
func decode(a) {
let jsonString = "{\"name\":\"zhangsan\", \"age\":15}" // Simulate JSON data
let decoder = JSONDecoder(a)let data = jsonString.data(using: .utf8)!
let model = try! decoder.decode(Demo.self, from: data)
print(model) // Demo(name: "zhangsan", age: 15)
}
Copy the code
The corresponding processing has been encapsulated in Moya’s Response
DemoProvider.provider.request(.zen) { (result) in
switch result {
case .success(let response):
if let model = try? response.map(Demo.self) {
success(model)
}
case .failure(let error):
break}}Copy the code
If the data is in several levels of JSON, it can also be retrieved by setting keypath:
{
data: {
name: "test",
age: 15}}try? response.map(Demo.self, atKeyPath: "data")
Copy the code
Note that the function also has an argument called failsOnEmptyData, which is set to true by default. If the data returned is empty, parsing will fail.
EndPoint
The EndPoint is half of Moya’s internal data structure, generated by the TargetType used, which is ultimately used to generate network requests. Each EndPoint stores the following data:
/// A string representation of the URL for the request.
public let url: String
/// A Closure Responsibility for Returning an 'EndpointSampleResponse'. (unit testing)
public let sampleResponseClosure: SampleResponseClosure
/// The HTTP method for the request.
public let method: Moya.Method
/// The `Task` for the request.
public let task: Task
/// The HTTP header fields for the request.
public let httpHeaderFields: [String: String]?
Copy the code
When the Provider is generated, you can pass in an endpointClosure to customize how the TargetType is delivered to the Endpoint.
Default implementation:
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
)
}
Copy the code
Here you can redefine how the Endpoint is generated, for example:
// Change all generated Endpoint requests to get
let endpointClosure = { (target: MyTarget) - >Endpoint in
let url = URL(target: target).absoluteString
return Endpoint(url: url, sampleResponseClosure: {.networkResponse(200, target.sampleData)}, method: .get, task: target.task)
}
Copy the code
Or modify the generated Endpoint:
let endpointClosure = { (target: MyTarget) - >Endpoint in
let defaultEndpoint = MoyaProvider.defaultEndpointMapping(for: target)
return defaultEndpoint.adding(newHTTPHeaderFields: ["APP_NAME": "MY_AWESOME_APP"])}Copy the code
Note: If you modify an already initialized Endpoint directly, you can only modify task and add headers.
Request
After an Endpoint is generated, it is converted to URLRequst for use.
The default implementation of Moya:
RequestResultClosure = (Result<URLRequest.MoyaError- > >)Void
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)))}}public func urlRequest(a) 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
......
Copy the code
Because inside have to realize how to generate the Request, most don’t need to modify the urlRequest, but redefine requestClosure, modify has generated good Request and below is directly modify the Request cache strategy, and error handling:
let requestClosure = { (endpoint: Endpoint, done: MoyaProvider.RequestResultClosure) in
do {
var request = try endpoint.urlRequest()
request.cachePolicy = .reloadIgnoringCacheData
done(.success(request))
} catch {
done(.failure(MoyaError.underlying(error)))
}
}
Copy the code
stubClosure
StubClosure implementation:
/// Do not stub.
final class func neverStub(_: Target) - >Moya.StubBehavior {
return .never
}
/// Return a response immediately.
final class func immediatelyStub(_: Target) - >Moya.StubBehavior {
return .immediate
}
/// Return a response after a delay.
final class func delayedStub(_ seconds: TimeInterval) - > (Target) - >Moya.StubBehavior {
return { _ in return .delayed(seconds: seconds) }
}
Copy the code
The default implementation of Moya is neverStub. When the immediatelyStub or delayedStub is used, the request to the network will not take the real data, but will return the data of SimpleData in Target, which is generally used to test the processing of data returned by the API.
DelayedStub specifies the delay time relative to the immediatelyStub, in seconds.
callbackQueue
You can specify the callback thread after the network request is returned. By default all requests will be put into the background thread by Alamofire and CallBAC will be called in the main thread.
Manager
public typealias Manager = Alamofire.SessionManager
final class func defaultAlamofireManager() - >Manager {
let configuration = URLSessionConfiguration.default
configuration.httpAdditionalHeaders = Manager.defaultHTTPHeaders
let manager = Manager(configuration: configuration)
manager.startRequestsImmediately = false
return manager
}
Copy the code
The Manager used in Moya is actually Alamofire’s Manager.
You can set a Timeout, cache policy, and so on
let manager: SessionManager = {
let configuration = defaultURLSessionConfiguration
configuration.requestCachePolicy = .reloadIgnoringLocalCacheData
configuration.timeoutIntervalForRequest = 20
let trustPolicyManager = ServerTrustPolicyManager(policies:
[
"www.baidu.com": ServerTrustPolicy.disableEvaluation
]
)
let manager = SessionManager(configuration: configuration, serverTrustPolicyManager: trustPolicyManager)
return manager
}()
Copy the code
Plugins
Plugins are plugins that follow the PluginType. A single provider can be used for multiple plugins.
PluginType:
public protocol PluginType {
/// There is still a chance to modify the request before sending it
func prepare(_ request: URLRequest, target: TargetType) -> URLRequest
/// called before sending
func willSend(_ request: RequestType, target: TargetType)
/// After the Response is received and before the callback is triggered
func didReceive(_ result: Result<Moya.Response, MoyaError>, target: TargetType)
/// Result can also be modified before Callback is called
func process(_ result: Result<Moya.Response, MoyaError>, target: TargetType) -> Result<Moya.Response.MoyaError>}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 }
}
Copy the code
There are many things you can do in the Plugin
- Logging network Requests
- Handles hiding or showing network Activity Progress
- Do more processing with the request
Such as:
struct TestPlugin: PluginType {
// Do more processing on the request
func prepare(_ request: URLRequest, target: TargetType) -> URLRequest {
var request = request
if target is GitHub {
request.timeoutInterval = 5
}
return request
}
// Log the network request
func willSend(_ request: RequestType, target: TargetType) {
print("start")
print(request.request? .url ??"")}// Log the network request
func didReceive(_ result: Result<Response, MoyaError>, target: TargetType) {
print("end")
switch result {
case .success(let response):
print("end success")
print(response.request? .url ??"")
case .failure(let error):
print("end failure")
print(error)
}
}
// Modify the result returned
func process(_ result: Result<Response, MoyaError>, target: TargetType) -> Result<Response.MoyaError> {
if case let .failure(error) = result {
return .failure(MoyaError.underlying(error, nil))}return result
}
}
Copy the code
Moya also provides plugins for Logger, activity, etc., which are implemented by default.
trackInflights
Read the source code for a long time or do not understand, hope to understand a friend can tell me how to use.
MultiTarget
Typically, a targetType corresponds to a Provider
let githubProvider = MoyaProvider<GitHub>(stubClosure: MoyaProvider.immediatelyStub, trackInflights: true)
let demoProvider = MoyaProvider<Demo>(stubClosure: MoyaProvider.immediatelyStub, trackInflights: true)
Copy the code
But if you want to make the Provider more generic, you can write:
let commonProvider = MoyaProvider<MultiTarget> ()Copy the code
When called, specify TargetType:
commonProvider.request(MultiTarget(GitHub.zen)) { result in. }Copy the code
process
Make up the flow chart I found online