moya(三)

moya(三)TargetsMoya 的使用始于定义一个 target 典型的是定义一个符合 TargetType 协议的枚举类型 然后 您的 APP 剩下的只处理那些 target Target 是一些你希望在 API 上采取的动作 比如 favoriteTwee tweetID String 这儿有个示例 publicenumGi casezencaseu

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" } } 

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 } } 

当我们谈论参数时,这里面隐含了参数需要被如何编码进我们的请求。我们需要通过.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)

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

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

还有 两个下载类型:

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 就像下面这样简单:

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

有两种方式与Endpoints交互。

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 提供了一个从TargetType到URL的便利扩展。

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

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

let endpointClosure = { 
    (target: MyTarget) -> Endpoint<MyTarget> in let defaultEndpoint = MoyaProvider.defaultEndpointMapping(for: target) return defaultEndpoint.adding(newHTTPHeaderFields: ["APP_NAME": "MY_AWESOME_APP"]) } 
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 有下面的这些值:

这个闭包接收一个Endpoint实例对象并负责调用把代表Endpoint的request作为参数的RequestResultClosure闭包 ( Result

-> 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))) } } 
{ 
    (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发送到网络之前每次都会被调用。

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

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

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

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。

更棒的是如果您需要对请求进行区别性的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) 
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 。

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) 

例如您可以通过传递 [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) } } 
let provider = MoyaProvider<YourAPI>(plugins: [CredentialsPlugin { 
    _ -> URLCredential? in return URLCredential(user: "user", password: "passwd", persistence: .none) } ]) 
let provider = MoyaProvider<YourAPI>(plugins: [CredentialsPlugin { 
    target -> URLCredential? in switch target { 
    case .targetThatNeedsAuthentication: return URLCredential(user: "user", password: "passwd", persistence: .none) default: return nil } } ]) 

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

您的 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, 是一个枚举值,代表用于请求的头

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 类型)

使用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订阅者在网络请求完成前被销毁了,那么这个请求将被取消 。

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

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

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订阅者在网络请求完成前被销毁了,那么这个请求将被取消 。

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

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

线程

默认,您所有的请求将会被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的插件是被用来编辑请求、响应及完成副作用的。 插件调用:

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

身份验证

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

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

这个插件可以在 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中找到

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/202394.html原文链接:https://javaforall.net

(0)
上一篇 2026年3月20日 上午7:15
下一篇 2026年3月20日 上午7:15


相关推荐

  • MemoryStream用法

    MemoryStream用法MemoryStream recvBuffer newMemoryStr recvBuffer Seek 0 SeekOrigin Begin recvBuffer SetLength recvBuffer Capacity byte buf recvBuffer GetBuffer intoffset int recvBuffer Position intsize int recvB

    2026年3月16日
    1
  • 常见的数据分析图表[通俗易懂]

    常见的数据分析图表[通俗易懂]常见的数据分析图表

    2022年5月3日
    153
  • emwin 汉字_emwin 弹出效果

    emwin 汉字_emwin 弹出效果emWin—显示汉字最近接触了emWin,需要做一个简单的界面,尝试在基于stm32f429的触摸屏上显示汉字,根据例程里面的操作,字库取模得到了C文件,添加到keil工程里面,最后在触摸屏上却没有显示任何汉字,对于emWin界面的程序结构一脸懵,最后发现有些小细节没有注意。1.字库取模①首先创建一个.txt文本文档,把需要显示的汉字添加进去,然后选择另存。②打开软件FontCvt,生成…

    2022年10月14日
    4
  • 阿里千问月活突破1亿:上线仅两个月,增速位列全球AI应用第一

    阿里千问月活突破1亿:上线仅两个月,增速位列全球AI应用第一

    2026年3月12日
    2
  • Spring Boot 使用 JAX-WS 调用 WebService 服务[通俗易懂]

    Spring Boot 使用 JAX-WS 调用 WebService 服务[通俗易懂]SpringBoot使用JAX-WS调用WebService服务1新建SpringBootMaven示例工程项目2自动生成JAX-WS代码除了CXF我们还可以使用SpringBoot自身默认的组件JAX-WS来实现WebService的调用。本项目源码github下载1新建SpringBootMaven示例工程项目注意:是用来…

    2022年7月15日
    43
  • 光栅化的算法实现

    光栅化的算法实现光栅化的算法实现一直用 OpenGL 绘制东西的时候 就会想到我们在写可编程管线的时候 都是使用 gl position 去保存一个物体经过 model 模型矩阵 矩阵 view 视口 矩阵以及 projection 投影 矩阵变换后的位置 然后利用该位置信息在片段着色器中为其上色并显示在屏幕上 这便是我们 OpenGL 实现光栅化的过程 那到底 OpenGL 底层是如何做到这一点呢 我们尝试着脱离 Open

    2026年3月26日
    2

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号