Moya的中文文档

文章摘自Moya官方文档

Targets

Moya的使用始于定义一个target——典型的是定义一个符合TargetType 协议的枚举类型。然后,您的APP剩下的只处理那些target。Target是一些你希望在API上采取的动作,比如 "favoriteTweet(tweetID: String)"。

这儿有个示例:

public enum GitHub {
    case zen
    case userProfile(String)
    case userRepositories(String)
    case branches(String, Bool)
}

Targets必须遵循 TargetType协议。 TargetType协议要求一个baseURL属性必须在这个枚举中定义,注意它不应该依赖于self的值,而应该直接返回单个值(如果您多个base URL,它们独立的分割在枚举和Moya中)。下面开始我们的扩展:

extension GitHub: TargetType {
    public var baseURL: URL { return URL(string: "https://api.github.com")! }
}

这个协议指定了你API端点相对于它base URL的位置(下面有更多的)

public var path: String {
    switch self {
    case .zen:
        return "/zen"
    case .userProfile(let name):
        return "/users/\(name.urlEscaped)"
    case .userRepositories(let name):
        return "/users/\(name.urlEscaped)/repos"
    case .branches(let repo, _)
        return "/repos/\(repo.urlEscaped)/branches"
    }
}

注意我们使用“_ ”符号,忽略了分支中的第二个关联值。这是因为我们不需要它来定义分支的路径。注意这儿我们使用了String的扩展urlEscaped
这个文档的最后会给出一个实现的示例。

OK, 非常好. 现在我们需要为枚举定义一个method, 这儿我们始终使用GET方法,所以这相当的简单:

public var method: Moya.Method {
    return .get
}

非常好. 如果您的一些端点需要POST或者其他的方法,那么您需要使用switch来分别返回合适的值。swith的使用在上面 path属性中已经看到过了。

我们的TargetType快成形了,但是我们还没有完成。我们需要一个task的计算属性。它返回可能带有参数的task类型。

下面是一个示例:

public var task: Task {
    switch self {
    case .userRepositories:
        return .requestParameters(parameters: ["sort": "pushed"], encoding: URLEncoding.default)
    case .branches(_, let protected):
        return .requestParameters(parameters: ["protected": "\(protected)"], encoding: URLEncoding.default)
    default:
        return .requestPlain
    }
}

不像我们先前的path属性, 我们不需要关心 userRepositories 分支的关联值, 所以我们省略了括号。
让我们来看下 branches 分支: 我们使用 Bool 类型的关联值(protected) 作为请求的参数值,并且把它赋值给了字典中的 "protected" 关键字。我们转换了 BoolString。(Alamofire 没有自动编码Bool参数, 所以需要我们自己来完成这个工作).

当我们谈论参数时,这里面隐含了参数需要被如何编码进我们的请求。我们需要通过.requestParameters中的ParameterEncoding参数来解决这个问题。Moya有 URLEncoding, JSONEncoding, and PropertyListEncoding可以直接使用。您也可以自定义编码,只要遵循ParameterEncoding协议即可(比如,XMLEncoder)。

task 属性代表你如何发送/接受数据,并且允许你向它添加数据、文件和流到请求体中。这儿有几种.request 类型:

  • .requestPlain 没有任何东西发送
  • .requestData(_:) 可以发送 Data (useful for Encodable types in Swift 4)
  • .requestJSONEncodable(_:)
  • .requestParameters(parameters:encoding:) 发送指定编码的参数
  • .requestCompositeData(bodyData:urlParameters:) & .requestCompositeParameters(bodyParameters:bodyEncoding:urlParameters) which allow you to combine url encoded parameters with another type (data / parameters)

同时, 有三个上传的类型:

  • .uploadFile(_:) 从一个URL上传文件,
  • .uploadMultipart(_:) multipart 上传
  • .uploadCompositeMultipart(_:urlParameters:) 允许您同时传递 multipart 数据和url参数

还有 两个下载类型:

  • .downloadDestination(_:) 单纯的文件下载
  • .downloadParameters(parameters:encoding:destination:) 请求中携带参数的下载。

下面, 注意枚举中的sampleData属性。 这是TargetType协议的一个必备属性。这个属性值可以用来后续的测试或者为开发者提供离线数据支持。这个属性值依赖于 self.

public 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)!
    case .userRepositories(let name):
        return "[{\"name\": \"Repo Name\"}]".data(using: String.Encoding.utf8)!
    case .branches:
        return "[{\"name\": \"master\"}]".data(using: String.Encoding.utf8)!
    }
}

最后, headers 属性存储头部字段,它们将在请求中被发送。

public var headers: [String: String]? {
    return ["Content-Type": "application/json"]
}

在这些配置后, 创建我们的 Provider 就像下面这样简单:

let GitHubProvider = MoyaProvider<GitHub>()

URLs的转义

这个扩展示例,需要您很容易的把常规字符串"like this" 转义成url编码的"like%20this"字符串:

extension String {
    var urlEscaped: String {
        return addingPercentEncoding(withAllowedCharacters: .urlHostAllowed)!
    }
}

(端点)Endpoints

endpoint是Moya的半个内部数据结构,它最终被用来生成网络请求。 每个endpoint 都存储了下面的数据:

  • url.
  • HTTP 方法 (GET, POST, etc).
  • HTTP 请求头.
  • Task 用来区别 upload, downloadrequest.
  • sample response (为单元测试).

Providers 映射 Targets 成 Endpoints, 然后映射
Endpoints 到实际的网络请求。

有两种方式与Endpoints交互。

  1. 当创建一个provider, 您可以指定一个从TargetEndpoint的映射.
  2. 当创建一个provider, 您可以指定一个从Endpoint to URLRequest的映射.

第一个可能类似如下:

let endpointClosure = { (target: MyTarget) -> Endpoint<MyTarget> in
    let url = URL(target: target).absoluteString
    return Endpoint(url: url, sampleResponseClosure: {.networkResponse(200, target.sampleData)}, method: target.method, task: target.task)
}

这实际上也Moya provide的默认实现。如果您需要一些定制或者创建一个在单元测试中返回一个非200HTTP状态的测试provide,这就是您需要自定义的地方。

注意 URL(target:) 的初始化, Moya 提供了一个从TargetTypeURL的便利扩展。

第二个使用非常的少见。Moya试图让您不用操心底层细节。但是,如果您需要,它就在那儿。它的使用涉及的更深入些.。

让我们来看一个从Target到EndpointLet的灵活映射的例子。

从 Target 到 Endpoint

在这个闭包中,您拥有从TargetEndpoint映射的绝对权利,
您可以改变task, method, url, headers 或者 sampleResponse
比如, 我们可能希望将应用程序名称设置到HTTP头字段中,从而用于服务器端分析。

let endpointClosure = { (target: MyTarget) -> Endpoint<MyTarget> in
    let defaultEndpoint = MoyaProvider.defaultEndpointMapping(for: target)
    return defaultEndpoint.adding(newHTTPHeaderFields: ["APP_NAME": "MY_AWESOME_APP"])
}
let provider = MoyaProvider<GitHub>(endpointClosure: endpointClosure)

注意头字段也可以作为Target定义的一部分。

这也就意味着您可以为部分或者所有的endpoint提供附加参数。 比如, 假设 MyTarget 除了实际执行身份验证的值之外,其他的所有值都需要有一个身份证令牌,我们可以构造一个类似如下面的
endpointClosure

let endpointClosure = { (target: MyTarget) -> Endpoint<MyTarget> in
    let defaultEndpoint = MoyaProvider.defaultEndpointMapping(for: target)

    // Sign all non-authenticating requests
    switch target {
    case .authenticate:
        return defaultEndpoint
    default:
        return defaultEndpoint.adding(newHTTPHeaderFields: ["AUTHENTICATION_TOKEN": GlobalAppStorage.authToken])
    }
}
let provider = MoyaProvider<GitHub>(endpointClosure: endpointClosure)

太棒了.

请注意,我们可以依赖于Moya的现有行为,而不是替换它。 adding(newHttpHeaderFields:) 函数允许您依赖已经存在的Moya代码并添加自定义的值 。

Sample responses 是 TargetType 协议的必备部分。然而, 它们仅指定返回的数据。在Target-到-Endpoint的映射闭包中您可以指定更多对单元测试非常有用的细节。

Sample responses 有下面的这些值:

  • .networkError(NSError) 当网络发送请求失败, 或者未能检索到响应 (比如 ,超时).
  • .networkResponse(Int, Data) 这个里面 Int 是一个状态码, Data 是返回的数据.
  • .response(HTTPURLResponse, Data) 这个里面 HTTPURLResponse 是一个 response , Data 是返回的数据. 这个可用来完全的stub一个响应。

Request 映射

我们先前已经提到过, 这个库的目标不是来提供一个网络访问的代码框架——那是Alamofire的事情。
Moya 是一种构建网络访问和为定义良好的网络目标提供编译时检查的方式。 您已经看到了如何使用MoyaProvider构造器中的endpointClosure参数把target映射成endpoint。这个参数让你创建一个 Endpoint 实例对象,Moya将会使用它来生成网络API调用。 在某一时刻,
Endpoint 必须被转化成 URLRequest 从而给到 Alamofire。
这就是 requestClosure 参数的作用.

requestClosure 是可选的,是最后编辑网络请求的时机 。 它有一个默认值MoyaProvider.defaultRequestMapping,
这个值里面仅仅使用了EndpointurlRequest 属性 .

这个闭包接收一个Endpoint实例对象并负责调用把代表Endpoint的request作为参数的RequestResultClosure闭包 ( Result<URLRequest, MoyaError> -> Void的简写) 。
在这儿,您要做OAuth签名或者别的什么。由于您可以异步调用闭包,您可以使用任何您喜欢的权限认证库,如 (example)。
//不修改请求,而是简单地将其记录下来。

let requestClosure = { (endpoint: Endpoint<GitHub>, done: MoyaProvider.RequestResultClosure) in
    do {
        var request = try endpoint.urlRequest()
        // Modify the request however you like.
        done(.success(request))
    } catch {
        done(.failure(MoyaError.underlying(error)))
    }

}
let provider = MoyaProvider<GitHub>(requestClosure: requestClosure)

requestClosure用来修改URLRequest的指定属性或者提供直到创建request才知道的信息(比如,cookie设置)给request是非常有用的。注意上面提到的endpointClosure 不是为了这个目的,也不是任何特定请求的应用级映射。

这个闭包参数实际在编辑请求对象时是非常有用的。
URLRequest 有很多你可以自定义的属性。比方,你想禁用所有请求的cookie:

{ (endpoint: Endpoint<ArtsyAPI>, done: MoyaProvider.RequestResultClosure) in
    do {
        var request: URLRequest = try endpoint.urlRequest()
        request.httpShouldHandleCookies = false
        done(.success(request))
    } catch {
        done(.failure(MoyaError.underlying(error)))
    }
}

您也可以在此完成网络请求的日志输出,因为这个闭包在request发送到网络之前每次都会被调用。

(供应者)Providers

当使用Moya时, 您通过MoyaProvider实例进行所有API请求,并把指定要调用哪个Endpoint的enum的值传递给它。在你设置了 Endpoint之后, 基本用法实际上配置完毕了:

let provider = MoyaProvider<MyService>()

在如此简单的设置之后您就可以直接使用了:

provider.request(.zen) { result in
    // `result` is either .success(response) or .failure(error)
}

到此完毕! request() 方法返回一个Cancellable, 它有一个你可以取消request的公共的方法。 更多关于Result类型的的信息查看 Examples

记住, 把target和provider放在哪儿完全取决于您自己。 您可以查看 Artsy的实现
的例子.

但是别忘了持有它的一个引用 . 如果它被销毁了你将会在response上看到一个 -999 "canceled" 错误 。

高级用法

为了解释 MoyaProvider所有的配置选项我们将会按照下面的小节一个一个的来解析 。

(endpoint闭包)endpointClosure:

MoyaProvider 构造器的第一个(可选的)参数是一个
endpoints闭包, 它负责把您的enum值映射成一个Endpoint实例对象。 让我们看看它是什么样子的。

let endpointClosure = { (target: MyTarget) -> Endpoint<MyTarget> in
    let url = URL(target: target).absoluteString
    return Endpoint(url: url, sampleResponseClosure: {.networkResponse(200, target.sampleData)}, method: target.method, task: target.task)
}
let provider = MoyaProvider(endpointClosure: endpointClosure)

注意在这个MoyaProvider的构造器中我们不再有指定泛型 ,因为Swift将会自动从endpointClosure的类型中推断出来。 非常灵巧!

您有可能已经注意到了URL(target:) 构造器, Moya 提供了一个便利扩展来从任意 TargetType中创建 URL

这个endpointClosure就像您看到的这样简单. 它其实也是Moya的默认实现, 这个实现存储在 MoyaProvider.defaultEndpointMapping.
查看 Endpoints 文档来查看 为什么 您可能想自定义这个。

(请求闭包)requestClosure:

下一个初始化参数是requestClosure,它分解一个Endpoint 成一个实际的 URLRequest. 同样的, 查看 Endpoints
文档了解为什么及如何来做这个 。

(stub闭包)stubClosure:

下一个选择是来提供一个stubClosure。这个闭包返回 .never (默认的), .immediate 或者可以把stub请求延迟指定时间的.delayed(seconds)三个中的一个。 例如, .delayed(0.2) 可以把每个stub 请求延迟0.2s. 这个在单元测试中来模拟网络请求是非常有用的。

更棒的是如果您需要对请求进行区别性的stub,那么您可以使用自定义的闭包。

let provider = MoyaProvider<MyTarget>(stubClosure: { target: MyTarget -> Moya.StubBehavior in
    switch target {
        /* Return something different based on the target. */
    }
})

但通常情况下,您希望所有目标都有同样的stub行为。在 MoyaProvider中有三个静态方法您可以使用。

MoyaProvider.neverStub
MoyaProvider.immediatelyStub
MoyaProvider.delayedStub(seconds)

所以,在上面的示例上,如果您希望为所有的target立刻进行stub行为,下面的两种方式都可行 。

let provider = MoyaProvider<MyTarget>(stubClosure: { (_: MyTarget) -> Moya.StubBehavior in return .immediate })
let provider = MoyaProvider<MyTarget>(stubClosure: MoyaProvider.immediatelyStub)

(管理器)manager:

接下来就是manager参数. 默认您将会获得一个基本配置的自定义的Alamofire.Manager实例对象

public final class func defaultAlamofireManager() -> Manager {
    let configuration = URLSessionConfiguration.default
    configuration.httpAdditionalHeaders = Alamofire.Manager.defaultHTTPHeaders

    let manager = Alamofire.Manager(configuration: configuration)
    manager.startRequestsImmediately = false
    return manager
}

这儿只有一个需要注意的事情: 由于在AF中创建一个Alamofire.Request默认会立即触发请求,即使为单元测试进行 "stubbing" 请求也一样。 因此在Moya中, startRequestsImmediately 属性被默认设置成了 false

如果您喜欢自定义自己的 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)

插件:

最后, 您可能也提供一个plugins数组给provider。 这些插件会在请求被发送前及响应收到后被执行。 Moya已经提供了一些插件: 一个是 网络活动(NetworkActivityPlugin),一个是记录所有的 网络活动 (NetworkLoggerPlugin), 还有一个是 HTTP Authentication.

例如您可以通过传递 [NetworkLoggerPlugin()]plugins参考来开启日志记录 。注意查看也可以配置的, 比如,已经存在的 NetworkActivityPlugin 需要一个 networkActivityClosure 参数. 可配置的插件实现类似这样的:

public final class NetworkActivityPlugin: PluginType {

    public typealias NetworkActivityClosure = (change: NetworkActivityChangeType) -> ()
    let networkActivityClosure: NetworkActivityClosure

    public init(networkActivityClosure: 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(change: .began)
    }

    /// Called by the provider as soon as a response arrives
    public func didReceive(data: Data?, statusCode: Int?, response: URLResponse?, error: ErrorType?, target: TargetType) {
        networkActivityClosure(change: .ended)
    }
}

networkActivityClosure 是一个当网络请求开始或结束时提供通知的闭包。 这个和 network activity indicator一起来用是非常有用的。
注意这个闭包的签名是 (change: NetworkActivityChangeType) -> (),
所以只有当请求是.began 或者.ended(您没有提供任何关于网络请求的细节) 时您才会被通知。

身份验证

身份验证变化多样。可以通过一些方法对网络请求进行身份验证。让我们来讨论常见的两种。

基本的HTTP身份验证

HTTP身份验证是一个 username/password HTTP协议内置的验证方式. 如果您需要使用 HTTP身份验证, 当初始化provider的时候可以使用一个 CredentialsPlugin

let provider = MoyaProvider<YourAPI>(plugins: [CredentialsPlugin { _ -> URLCredential? in
        return URLCredential(user: "user", password: "passwd", persistence: .none)
    }
])

这个特定的例子显示了HTTP的使用,它验证 每个 请求,
通常这是不必要的。下面的方式可能更好:

let provider = MoyaProvider<YourAPI>(plugins: [CredentialsPlugin { target -> URLCredential? in
        switch target {
        case .targetThatNeedsAuthentication:
            return URLCredential(user: "user", password: "passwd", persistence: .none)
        default:
            return nil
        }
    }
])

访问令牌认证

另一个常见的身份验证方法就是通过使用一个访问令牌。
Moya提供一个 AccessTokenPlugin 来完成
JWTBearer 认证 和 Basic 认证 。

开始使用AccessTokenPlugin之前需要两个步骤.

  1. 您需要把 AccessTokenPlugin 添加到您的MoyaProvider中,就像下面这样:
let token = "eyeAm.AJsoN.weBTOKen"
let authPlugin = AccessTokenPlugin(tokenClosure: token)
let provider = MoyaProvider<YourAPI>(plugins: [authPlugin])

AccessTokenPlugin 构造器接收一个tokenClosure闭包来负责返回一个可以被添加到request头部的令牌 。

  1. 您的 TargetType 需要遵循AccessTokenAuthorizable 协议:
extension YourAPI: TargetType, AccessTokenAuthorizable {
    case targetThatNeedsBearerAuth
    case targetThatNeedsBasicAuth
    case targetDoesNotNeedAuth

    var authorizationType: AuthorizationType {
        switch self {
            case .targetThatNeedsBearerAuth:
                return .bearer
            case .targetThatNeedsBasicAuth:
                return .basic
            case .targetDoesNotNeedAuth:
                return .none
            }
        }
}

AccessTokenAuthorizable 协议需要您实现一个属性 , authorizationType, 是一个枚举值,代表用于请求的头

Bearer HTTP 认证
Bearer 请求通过向HTTP头部添加下面的表单来获得授权:

Authorization: Bearer <token>

Basic API Key 认证
Basic 请求通过向HTTP头部添加下面的表单来获得授权

Authorization: Basic <token>

OAuth

OAuth 有些麻烦。 它涉及一个多步骤的过程,在不同的api之间通常是不同的。 您 确实 不想自己来做OAuth –
这儿有其他的库为您服务. Heimdallr.swift,
例如. The trick is just getting Moya and whatever you're using to talk
to one another.

Moya内置了OAuth思想。 使用OAuth的网络请求“签名”本身有时会要求执行网络请求,所以对Moya的请求是一个异步的过程。让我们看看一个例子。

let requestClosure = { (endpoint: Endpoint<YourAPI>, done: MoyaProvider.RequestResultClosure) in
    let request = endpoint.urlRequest // This is the request Moya generates
    YourAwesomeOAuthProvider.signRequest(request, completion: { signedRequest in
        // The OAuth provider can make its own network calls to sign your request.
        // However, you *must* call `done()` with the signed so that Moya can
        // actually send it!
        done(.success(signedRequest))
    })
}
let provider = MoyaProvider<YourAPI>(requestClosure: requestClosure)

(注意 Swift能推断出您的 YourAPI 类型)

在您的Provider子类中处理session刷新

您可以查看在每个请求前session刷新的示例Examples/SubclassingProvider.
它是基于 Artsy's networking implementation.

ReactiveSwift

Moya在MoyaProvider中提供了一个可选的ReactiveSwift 实现,它可以做些有趣的事情。我们使用SignalProducer而不使用request()及请求完成时的回调闭包。

使用reactive扩展您不需要任何额外的设置。只使用您的 MoyaProvider实例对象 。

let provider = MoyaProvider<GitHub>()

简单设置之后, 您就可以使用了:

provider.reactive.request(.zen).start { event in
    switch event {
    case let .value(response):
        // do something with the data
    case let .failed(error):
        // handle the error
    default:
        break
    }
}

您也可以使用 requestWithProgress 来追踪您请求的进度 :

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):
        // handle the error
    default:
        break
    }
}

请务必记住直到signal被订阅之后网络请求才会开始。signal订阅者在网络请求完成前被销毁了,那么这个请求将被取消 。

如果请求正常完成,两件事件将会发生:

  1. 这个信号将发送一个值,即一个 Moya.Response 实例对象.
  2. 信号结束.

如果这个请求产生了一个错误 (通常一个 URLSession 错误),
然后它将发送一个错误. 这个错误的 code 就是失败请求的状态码, if any, and the response data, if any.

Moya.Response 类包含一个 statusCode, 一个 data,
和 一个( 可选的) HTTPURLResponse. 您可以在 startWithNextmap 回调中随意使用这些值.

为了让事情更加简便, Moya 为SignalProducer提供一些扩展来更容易的处理Moya.Responses

  • filter(statusCodes:) 指定一范围的状态码。如果响应的状态代码不是这个范围内,会产生一个错误。
  • filter(statusCode:) 查看指定的一个状态码,如果没找到会产生一个错误。
  • filterSuccessfulStatusCodes() 过滤 200-范围内的状态码.
  • filterSuccessfulStatusAndRedirectCodes() 过滤 200-300 范围内的状态码。
  • mapImage() 尝试把响应数据转化为 UIImage 实例
    如果不成功将产生一个错误。
  • mapJSON() 尝试把响应数据映射成一个JSON对象,如果不成功将产生一个错误。
  • mapString() 把响应数据转化成一个字符串,如果不成功将产生一个错误。
  • mapString(atKeyPath:) 尝试把响应数据的key Path 映射成一个字符串,如果不成功将产生一个错误。

在错误的情况下, 错误的 domainMoyaErrorDomain。code
的值是MoyaErrorCode的其中一个的rawValue值。 只要有可能,会提供underlying错误并且原始响应数据会被包含在NSError的字典类型的userInfo的data中

RxSwift

Moya 在MoyaProvider中提供了一个可选的RxSwift实现,它可以做些有趣的事情。我们使用 Observable而不使用request()及请求完成时的回调闭包。

使用reactive扩展您不需要任何额外的设置。只使用您的 MoyaProvider实例对象 。

let provider = MoyaProvider<GitHub>()

简单设置之后, 您就可以使用了:

provider.rx.request(.zen).subscribe { event in
    switch event {
    case .success(let response):
        // do something with the data
    case .error(let error):
        // handle the error
    }
}

您也可以使用 requestWithProgress 来追踪您请求的进度 :

provider.rx.requestWithProgress(.zen).subscribe { event in
    switch event {
    case .next(let progressResponse):
        if let response = progressResponse.response {
            // do something with response
        } else {
            print("Progress: \(progressResponse.progress)")
        }
    case .error(let error):
        // handle the error
    default:
        break
    }
}

请务必记住直到signal被订阅之后网络请求才会开始。signal订阅者在网络请求完成前被销毁了,那么这个请求将被取消 。

如果请求正常完成,两件事件将会发生:

  1. 这个信号将发送一个值,即一个 Moya.Response 实例对象.
  2. 信号结束.

如果这个请求产生了一个错误 (通常一个 URLSession 错误),
然后它将发送一个错误. 这个错误的 code 就是失败请求的状态码, if any, and the response data, if any.

Moya.Response 类包含一个 statusCode, 一个 data,
和 一个( 可选的) HTTPURLResponse. 您可以在 subscribemap 回调中随意使用这些值。

为了让事情更加简便, Moya 为SingleObservable提供一些扩展来更容易的处理MoyaResponses

  • filter(statusCodes:) 指定一范围的状态码。如果响应的状态代码不是这个范围内,会产生一个错误。
  • filter(statusCode:) 查看指定的一个状态码,如果没找到会产生一个错误。
  • filterSuccessfulStatusCodes() 过滤 200-范围内的状态码.
  • filterSuccessfulStatusAndRedirectCodes() 过滤 200-300 范围内的状态码。
  • mapImage() 尝试把响应数据转化为 UIImage 实例
    如果不成功将产生一个错误。
  • mapJSON() 尝试把响应数据映射成一个JSON对象,如果不成功将产生一个错误。
  • mapString() 把响应数据转化成一个字符串,如果不成功将产生一个错误。
  • mapString(atKeyPath:) 尝试把响应数据的key Path 映射成一个字符串,如果不成功将产生一个错误。

在错误的情况下, 错误的 domainMoyaErrorDomain。code
的值是MoyaErrorCode的其中一个的rawValue值。 只要有可能,会提供underlying错误并且原始响应数据会被包含在NSError的字典类型的userInfo的data中

线程

默认,您所有的请求将会被Alamofire放入background线程中, 响应将会在主线程中调用。如果您希望您的响应在不同的线程中调用 , 您可以用一个指定的 callbackQueue来初始化您的provider:

provider = MoyaProvider<GitHub>(callbackQueue: DispatchQueue.global(.utility))
provider.request(.userProfile("ashfurrow")) {
    /* this is called on a utility thread */
}

使用 RxSwift 或者 ReactiveSwift 您可以使用 observeOn(_:) 或者 observe(on:) 来实现类似的的行为:

RxSwift

provider = MoyaProvider<GitHub>()
provider.rx.request(.userProfile("ashfurrow"))
  .map { /* this is called on the current thread */ }
  .observeOn(ConcurrentDispatchQueueScheduler(qos: .utility))
  .map { /* this is called on a utility thread */ }

ReactiveSwift

provider = MoyaProvider<GitHub>()
provider.reactive.request(.userProfile("ashfurrow"))
  .map { /* this is called on the current thread */ }
  .observe(on: QueueScheduler(qos: .utility))
  .map { /* this is called on a utility thread */ }

插件

Moya的插件是被用来编辑请求、响应及完成副作用的。 插件调用:

  • (prepare) Moya 已经分解 TargetTypeURLRequest之后被执行.
    这是请求被发送前进行编辑的一个机会 (例如 添加
    headers).
  • (willSend) 请求将要发送前被执行. 这是检查请求和执行任何副作用(如日志)的机会。
  • (didReceive) 接收到一个响应后被执行. 这是一个检查响应和执行副作用的机会。
  • (process) 在 completion 被调用前执行. 这是对requestResult进行任意编辑的一个机会。

内置插件

Moya附带了一些用于常见功能的默认插件: 身份验证, 网络活动指示器管理 和 日志记录.
您可以在构造provider的时候申明插件来使用它:

let provider = MoyaProvider<GitHub>(plugins: [NetworkLoggerPlugin(verbose: true)])

身份验证

身份验证插件允许用户给每个请求赋值一个可选的 URLCredential 。当收到请求时,没有操作

这个插件可以在 Sources/Moya/Plugins/CredentialsPlugin.swift中找到

网络活动指示器

在iOS网络中一个非常常见的任务就是在网络请求是显示一个网络活动指示器,当请求完成时移除它。提供的插件添加了回调,当请求开始和结束时调用,它可以用来跟踪正在进行的请求数,并相应的显示和隐藏网络活动指示器。

这个插件可以在 Sources/Moya/Plugins/NetworkActivityPlugin.swift中找到

日志记录

在开发期间,将网络活动记录到控制台是非常有用的。这可以是任何来自发送和接收请求URL的内容,来记录每个请求和响应的完整的header,方法,请求体。

The provided plugin for logging is the most complex of the provided plugins, and can be configured to suit the amount of logging your app (and build type) require. When initializing the plugin, you can choose options for verbosity, whether to log curl commands, and provide functions for outputting data (useful if you are using your own log framework instead of print) and formatting data before printing (by default the response will be converted to a String using String.Encoding.utf8 but if you'd like to convert to pretty-printed JSON for your responses you can pass in a formatter function, see the function JSONResponseDataFormatter in Demo/Shared/GitHubAPI.swift for an example that does exactly that)

这个插件可以在 Sources/Moya/Plugins/NetworkLoggerPlugin.swift中找到

自定义插件

Every time you need to execute some pieces of code before a request is sent and/or immediately after a response, you can create a custom plugin, implementing the PluginType protocol.
For examples of creating plugins, see docs/Examples/CustomPlugin.md and docs/Examples/AuthPlugin.md.

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,569评论 4 363
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,499评论 1 294
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,271评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,087评论 0 209
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,474评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,670评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,911评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,636评论 0 202
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,397评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,607评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,093评论 1 261
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,418评论 2 254
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,074评论 3 237
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,092评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,865评论 0 196
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,726评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,627评论 2 270

推荐阅读更多精彩内容