6,926
•Moya是一个纯粹的轻量级网络层。依赖于Alamofire(目前Swift最火的网络框架,对比AFNetworking)上封装。Moya是一个高度抽象的网络库,理念是让你不用关心网络请求的底层的实现细节,只用定义你关心的业务。 •使用的好处:
1.网络层和业务层解耦分离
2.单元测试
3.规范&&稳定 • •官方给出几个Moya主要优点: •编译时检查API endpoint权限 •让你使用枚举定义各种不同Target, endpoints •把stubs当做一等公民对待,因此测试超级简单。
Moya的认识
普通的Alamofire网络请求
let parameters: Parameters = [ "foo": [1,2,3], "bar": [ "baz": "qux" ] ]
Alamofire.request("https://httpbin.org/post", method: .post, parameters: parameters, encoding: JSONEncoding.default)
Moya的网络请求:
provider = MoyaProvider<GitHub>() provider.request(.userProfile("ashfurrow")) { result in // do something with the result }
本文的学习过程
一.Moya的实现流程图
二.Moya的内部实现过程&使用过程
三.Moya的扩展— >RectiveSwift
一.Moya的实现流程图
Moya的主要实现流程是将用户设置好的Target类型转换成EndPoint再转换成URLRequest给Alamofire做请求,然后将模拟数据(用户设置测试模式)或者Alamofire的返回数据返回到业务层。
二.Moya的内部实现过程&使用过程
1.target
我们从整个流程的入口开始学习,Target是一个遵守TargetType的枚举。这个TargetType协议要求Target设定服务器地址,每一个请求的地,请求方式,样式返回参数(测试使用),任务参数,是否执行Alamofire验证 和请求的头部信息。具体的协议内容如下:
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 }
/// Whether or not to perform Alamofire validation. Defaults to `false`.
var validate: Bool { get }
// The headers to be used in the request.
var headers: [String: String]? { get }
一般我们制造这个Target的方式如下:
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 "lala".data(using: String.Encoding.utf8)!
// return "Half measures are as bad as nothing at all.".data(using: String.Encoding.utf8)!
case .userProfile(let name):
return "".data(using: String.Encoding.utf8)!
// return "{\"login\": \"\(name)\", \"id\": 100}".data(using: String.Encoding.utf8)!
var validate: Bool {
return true
var headers: [String: String]? {
return nil
一个扩展的枚举,这个枚举遵守了TargetType协议,执行了它里面的每一个函数。给需要的接口设定了对应的信息。当然,在实际的开发过程中,接口分业务模块有很多很多类型的接口,我们可以利用扩展,将不同业务的接口分开管理。也不需要执行重复的协议方法。
2.Provider——–Moya的核心类
Provider的作用
Provider真正做的事情可以用一个流来表示:Target -> Endpoint -> Request 。在这个例子中,它将Target转换成Endpoint, 再将其转换成为NSRURLRequest。最后将这个NSRURLRequest交给Alamofire去进行网络请求。
Provider初始化的时候做了2个事情:
1.你指定了Target到Endpoint的映射。
2.你指定了Endpoint到URLRequest的映射。
首先,我们看看Provider的初始化函数
public init(endpointClosure: @escaping EndpointClosure = MoyaProvider.defaultEndpointMapping,
requestClosure: @escaping RequestClosure = MoyaProvider.defaultRequestMapping,
stubClosure: @escaping StubClosure = MoyaProvider.neverStub,
callbackQueue: DispatchQueue? = nil,
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
self.callbackQueue = callbackQueue
Provider获得了好3个闭包。
1.我们发现的是:endpointClosure、requestClosure、stubClosure。这3个Closure是让我们定制请求或进行测试时用的。
2.然后是一个Manager,Manager是真正用来网络请求的类,Moya自己并不提供Manager类,Moya只是对其他网络请求类进行了简单的桥接。这么做是为了让调用方可以轻易地定制、更换网络请求的库。比如你不想用Alamofire,可以十分简单的换成其他库
3.最后是一个类型为PluginType的数组。Moya提供了一个插件机制,使我们可以建立自己的插件类来做一些额外的事情。比如写Log,显示“菊花”等。抽离出Plugin层的目的,就是让Provider职责单一,满足开闭原则。把和自己网络无关的行为抽离。避免各种业务揉在一起不利于扩展。
以下是初始化参数的认识:
1. EndPointClosure的作用,默认值 和 定制
EndpointClosure这个闭包,输入是一个Target,返回Endpoint。这就是我们前面说Provider指定Target 到Endpoint的映射
Endpoint 是Moya最终进行网络请求前的一种数据结构,它保存了这些数据:
•URL •HTTP请求方式 (GET, POST, etc). •本次请求的参数 •参数的编码方式 (URL, JSON, custom, etc). •stub数据的 response(测试用的)
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
类函数, 这个函数输入一个Target类型(范型,这个类型由外部决定,在Provider实例化的时候设定,官方的例子里Target类型就是Github)的target, 输出一个Endpoint, 这个函数将是Provider的默认参数。将赋给一个闭包参数。
默认闭包的代码只是单纯地创建并返回一个Endpoint实例。然而在很多时候,我们需要自定义这个闭包来做更多额外的事情。例如给客户端返回一个非200的状态码。为了实现这个功能,就需要在这个闭包里处理相关的逻辑!或者说这个闭包就是让我们根据业务需求定制网络请求的
以下例子是添加头参数
let endpointClosure = { (target: GitHub) -> Endpoint<GitHub> in
let defaultEndpoint = MoyaProvider.defaultEndpointMapping(for: target)
return defaultEndpoint.adding(newHTTPHeaderFields: ["APP_NAME": "MY_AWESOME_APP"])
let provider = MoyaProvider<GitHub>(endpointClosure: endpointClosure)
以下是添加认证Token的例子
let endpointClosure = { (target: GitHub) -> Endpoint<GitHub> in
let defaultEndpoint = MoyaProvider.defaultEndpointMapping(for: target)
// Sign all non-authenticating requests
switch target {
case .zen:
return defaultEndpoint
default:
return defaultEndpoint.adding(newHTTPHeaderFields: ["AUTHENTICATION_TOKEN": GlobalAppStorage.authToken])
let provider = MoyaProvider<GitHub>(endpointClosure: endpointClosure)
2. requestClosure的作用,默认值 和 定制
Endpoint到Request的映射
public final class func defaultRequestMapping(for endpoint: Endpoint<Target>, closure: RequestResultClosure) {
if let urlRequest = endpoint.urlRequest {
closure(.success(urlRequest))
} else {
closure(.failure(MoyaError.requestMapping(endpoint.url)))
简单地调用endpoint.urlRequest取得一个NSURLRequest实例。然后调用了closure回调
用于修改针对URLRequest的属性修改 或者 提供信息给请求(必须请求生成)。例如Cookies设置。
let reuestClosure = { (endpoint: Endpoint<GitHub>, done: MoyaProvider.RequestResultClosure) in
var request: URLRequest = endpoint.urlRequest!
request.httpShouldHandleCookies = false
done(.success(request))
let provider = MoyaProvider<GitHub>(requestClosure: reuestClosure)
3. requestClosure的作用
StubClosure这个闭包比较简单,返回一个StubBehavior的枚举值。它就是让你告诉Moya你是否使用Stub返回数据或者怎样使用Stub返回数据
public enum StubBehavior {
/// Do not stub.
case never
/// Return a response immediately.
case immediate
/// Return a response after a delay.
case delayed(seconds: TimeInterval)
Never表明不使用Stub来返回模拟的网络数据,Immediate表示马上返回Stub的数据,Delayed是在几秒后返回。Moya默认是不使用Stub来测试。
在Target那一节我们定义了一个GitHub, 我们实现了接口sampleData,这个属性是返回Stub数据的。
Moya先调用我们在构造函数中传入的stubClosure闭包,如果stubBehavior是Never就真正的发起网络请求,否
者就调用self.stubRequest
如果Immediate,就马上调用stub返回,是Delayed的话就Dispatch after延迟调用。
4.Manager的作用, 默认值 和 定制
Moya旨在隐藏Alamofire,做了桥接。也便于脱离Alamofire。用了2种方式:
1.别名方式
public typealias Manager = Alamofire.SessionManager
internal typealias Request = Alamofire.Request
internal typealias DownloadRequest = Alamofire.DownloadRequest
internal typealias UploadRequest = Alamofire.UploadRequest
internal typealias DataRequest = Alamofire.DataRequest
internal typealias URLRequestConvertible = Alamofire.URLRequestConvertible
/// Represents an HTTP method.
public typealias Method = Alamofire.HTTPMethod
/// Choice of parameter encoding.
public typealias ParameterEncoding = Alamofire.ParameterEncoding
public typealias JSONEncoding = Alamofire.JSONEncoding
public typealias URLEncoding = Alamofire.URLEncoding
public typealias PropertyListEncoding = Alamofire.PropertyListEncoding
/// Multipart form
public typealias RequestMultipartFormData = Alamofire.MultipartFormData
/// Multipart form data encoding result.
public typealias MultipartFormDataEncodingResult = Manager.MultipartFormDataEncodingResult
public typealias DownloadDestination = Alamofire.DownloadRequest.DownloadFileDestination
2.抽象了一个RequestType协议,利用这个协议将Alamofire隐藏了起来,让Provider类依赖于这个协议 ,然后让Moya.Manager == Alamofire.Manager,并且让Alamofire.Manager也实现RequestType协议
public protocol RequestType {
// Note:
// We use this protocol instead of the Alamofire request to avoid leaking that abstraction.
// A plugin should not know about Alamofire at all.
/// Retrieve an `NSURLRequest` representation.
var request: URLRequest? { get }
/// Authenticates the request with a username and password.
func authenticate(user: String, password: String, persistence: URLCredential.Persistence) -> Self
/// Authenticates the request with an `NSURLCredential` instance.
func authenticate(usingCredential credential: URLCredential) -> Self
完成了Alamofire的封装、桥接。正因为桥接封装了Alamofire,因此Moya的request,最终一定会调用Alamofire的request
func sendRequest(_ target: Target, request: URLRequest, callbackQueue: DispatchQueue?, progress: Moya.ProgressBlock?, completion: @escaping Moya.Completion) -> CancellableToken {
let initialRequest = manager.request(request as URLRequestConvertible)
let alamoRequest = target.validate ? initialRequest.validate() : initialRequest
return sendAlamofireRequest(alamoRequest, target: target, callbackQueue: callbackQueue, progress: progress, completion: completion)
public final class func defaultAlamofireManager() -> Manager {
let configuration = URLSessionConfiguration.default
configuration.httpAdditionalHeaders = Manager.defaultHTTPHeaders
let manager = Manager(configuration: configuration)
manager.startRequestsImmediately = false
return manager
如果您喜欢自定义自己的 manager, 比如, 添加SSL pinning, 创建一个并且添加到manager, 所有请求将通过自定义配置的manager进行路由.
let policies: [String: ServerTrustPolicy] = [ "example.com": .PinPublicKeys(
publicKeys: ServerTrustPolicy.publicKeysInBundle(),
validateCertificateChain: true,
validateHost: true
let manager = Manager(configuration: URLSessionConfiguration.default,
serverTrustPolicyManager: ServerTrustPolicyManager(policies: policies)
let provider = MoyaProvider<MyTarget>(manager: manager)
5. 插件
最后,你可能会想提供一系列的插件给Provider,那些发送请求前和接受相应后的回调!!
自定义插件必须要遵从PluginType协议,协议的回调暴露了请求的过程,具体函数如下:
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>
Moya已经有一些插件供使用,一个是网络activity(NetworkActivityPlugin),一个是网络活动日记(NetworkLoggerPlugin),和HTTP认证。 •例如你可以简单地加入[NetworkLoggerPlugin()]在你Endpoint的插件参数旁边。 注意:插件也是可设置的,例如已经加入了NetworkActivityPlugin 需要一个 networkActivityClosure 参数。这个设置插件实现如下:
// Network activity change notification type.
public enum NetworkActivityChangeType {
case began, ended
/// Notify a request's network activity changes (request begins or ends).
public final class NetworkActivityPlugin: PluginType {
public typealias NetworkActivityClosure = (_ change: NetworkActivityChangeType) -> Void
let networkActivityClosure: NetworkActivityClosure
public init(networkActivityClosure: @escaping NetworkActivityClosure) {
self.networkActivityClosure = networkActivityClosure
// MARK: Plugin
/// Called by the provider as soon as the request is about to start
public func willSend(_ request: RequestType, target: TargetType) {
networkActivityClosure(.began)
/// Called by the provider as soon as a response arrives, even if the request is canceled.
public func didReceive(_ result: Result<Moya.Response, MoyaError>, target: TargetType) {
networkActivityClosure(.ended)
使用的时候:
let network = { (_ change: NetworkActivityChangeType) -> Void in
switch change {
case .began:
print("我是自定义的网络监控————开始啦")
default:
print("我是自定义的网络监控---结束啦")
let activity:NetworkActivityPlugin = NetworkActivityPlugin(networkActivityClosure:network)
let provider = MoyaProvider<GitHub>(endpointClosure: endpointClosure,plugins:[activity])
Moya官方还提供了请求的日记打印插件 NetworkLoggerPlugin.
可以直接使用。
let provider = MoyaProvider<GitHub>(plugins: [NetworkLoggerPlugin(verbose: true)])
例外还有插件:
Sources/Moya/Plugins/CredentialsPlugin.swift
当你想自定义插件的时候,可查阅:
docs/Examples/CustomPlugin.md
docs/Examples/AuthPlugin.md.
ReactiveSwift响应式编程
•Moya提供了Provider的选择性ReactiveSwift执行,这个能做一些好玩的东西。不再使用调用request()方法和请求完成后提供回调,使用了SignalProducer s。
•使用reactive扩展,我们不需要任何的装载,直接使用MoyaProvider的实例对象。
注意:Pod文件需要
pod 'Moya/ReactiveSwift'
provider.reactive.request(.zen).start { event in
switch event {
case let .value(response): break
// do something with the data
case let .failed(error): break
// handle the error
default:
break
provider.reactive.requestWithProgress(.zen).start { event in
switch event {
case .value(let progressResponse):
if let response = progressResponse.response {
// do something with response
} else {
print("Progress: \(progressResponse.progress)")
case .failed(let error): break
print(error)
// handle the error
default:
break
有一点需要记得:
请求在没有信号订阅的情况下不会进行请求。如果定远在请求完成前被丢弃,请求会被取消。
如果请求正常完成,两个事情发生:
1.信号发送值,一个Moya.Response对象
2.信号完成
如果请求生成一个错误(特别氏URLSession错误),那么它会发送一个error。错误代码是请求失败代码,还有data
请求结果的处理
•Moya.response类包含有statusCode,data和一个(n 选择性的)HTTPURLResponse。
•Moya提供了SinalProducer的扩张去相当容易地处理Moya.response,分别有:
•filter(statusCodes:) 获取某个状态码区间的相应,如果超出,返回error.
•filter(statusCode:) 寻找指定的状态嘛,其它的返回error.
•filterSuccessfulStatusCodes() 过滤200-range间的状态码.
•filterSuccessfulStatusAndRedirectCodes() 过滤200-300的状态码.
•mapImage() 尝试映射响应到UIImage实例,失败返回error.
•mapJSON() 尝试映射响应到JSON对象,失败返回error.
•mapString() 尝试映射响应到String,失败返回error.
•mapString(atKeyPath:) 尝试映射一个响应data的对应key的值成一个String,失败返回error.
请求结果处理—error说明
•在Error里,error的domain是MoyaErrorDomain。 码是MoyaErrorCode 的其中一个rawValue。
•只要有可能,就会提供潜在的错误和原始响应数据在NSError的userinfo字典的对应data的值里。
学习了swift很受欢迎网络封装层(Moya),它给网络层提供了规范,稳定,易扩展,易管理等性能!期间使用了闭包回调,类映射(脱耦),枚举应用,范型使用等让我更深地认识Swift以及感觉到它的强大。
Moya主要供功能包括如下:
1.分离App业务层和网络层,统一管理(枚举)接口文件
2.提供闭包自定义设定Provider配置&&插件扩展,覆盖了网络常见的功能(如:SSL,请求头管理,Token认证,Cookie设置,验证码过滤,网络过层监控,运行日记,映射功能,单元测试等)
Tagged iOS, Moya, Swift