Moya 浅析_motivationally

Moya 浅析_motivationallyMoya是一个高度抽象的网络库,他的理念是让你不用关心网络请求的底层的实现细节,只用定义你关心的业务。且Moya采用桥接和组合来进行封装(默认桥接了Alamofire),使得Moya非常好扩展,让你不用修改Moya源码就可以轻易定制。官方给出几个Moya主要优点:编译时检查APIendpoint权限让你使用枚举定义各种不同Target,endpoints把stubs当做…

大家好,又见面了,我是你们的朋友全栈君。如果您正在找激活码,请点击查看最新教程,关注关注公众号 “全栈程序员社区” 获取激活教程,可能之前旧版本教程已经失效.最新Idea2022.1教程亲测有效,一键激活。

Jetbrains全家桶1年46,售后保障稳定

Moya是一个高度抽象的网络库,他的理念是让你不用关心网络请求的底层的实现细节,只用定义你关心的业务。且Moya采用桥接和组合来进行封装(默认桥接了Alamofire),使得Moya非常好扩展,让你不用修改Moya源码就可以轻易定制。官方给出几个Moya主要优点:

  • 编译时检查API endpoint权限
  • 让你使用枚举定义各种不同Target, endpoints
  • 把stubs当做一等公民对待,因此测试超级简单。

Target

开始Moya之旅的第一步便是,建立一个Enum的Target,这个Target便是你网络请求相关行为的定义。Target必须实现TargetType协议。

public protocol TargetType { var baseURL: NSURL { get } var path: String { get } var method: Moya.Method { get } var parameters: [String: AnyObject]? { get } var sampleData: NSData { get } } 

Jetbrains全家桶1年46,售后保障稳定

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

例如有一个AccountAPI模块,模块实现注册登录的功能。所以第一件事情,我们需要定义一个Target

enum AccountAPI {
    case Login(userName: String, passwd: String) case Register(userName: String, passwd: String) } extension AccountAPI: TargetType { var baseURL: NSURL { return NSURL(string: "https://www.myapp.com")! } var path: String { switch self { case .Login: return "/login" case .Register: return "/register" } } var method: Moya.Method { return .GET } var parameters: [String: AnyObject]? { switch self { case .Login: return nil case .Register(let userName, let passwd): return ["username": userName, "password": passwd] } } var sampleData: NSData { switch self { case .Login: return "{'code': 1,6'Token':'123455'}".dataUsingEncoding(NSUTF8StringEncoding)! case .Register(let userName, let passwd): return "找不到数据" } } }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41

主要是实现了TargetType协议,里面的网址和内容,是随便写的,可能不make sence(不合理), 但 仅仅是做一个例子而已。

Providers

Providers是Moya中的核心,Moya中所有的API请求都是通过Provider来发起的。因此大多数时候,你的代码请求像这样:

let provider = MoyaProvider<AccountAPI>()
provider.request(.Login) { result in // `result` is either .Success(response) or .Failure(error) } 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 1
  • 2
  • 3
  • 4
  • 5

我们初始化了一个AccountAPI的Provider,并且调用了Login请求。怎么样?干净简单吧!

从Provider的构造函数说起

Provider真正做的事情可以用一个流来表示:Target -> Endpoint -> Request 。在这个例子中,它将AccountAPI转换成Endpoint, 再将其转换成为NSRURLRequest。最后将这个NSRURLRequest交给Alamofire去进行网络请求。

我们从Provider的构造函数开始切入,一步一步地扒开它。

//Moya.swift public init(endpointClosure: EndpointClosure = MoyaProvider.DefaultEndpointMapping, requestClosure: RequestClosure = MoyaProvider.DefaultRequestMapping, stubClosure: StubClosure = MoyaProvider.NeverStub, manager: Manager = MoyaProvider<Target>.DefaultAlamofireManager(), plugins: [PluginType] = []) 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  1. 首先我们发现的是3个Closure:endpointClosure、requestClosure、stubClosure。这3个Closure是让我们定制请求和进行测试时用的。非常有用,后面细说。

  2. 然后是一个Manager,Manager是真正用来网络请求的类,Moya自己并不提供Manager类,Moya只是对其他网络请求类进行了简单的桥接。这么做是为了让调用方可以轻易地定制、更换网络请求的库。比如你不想用Alamofire,可以十分简单的换成其他库

  3. 最后是一个类型为PluginType的数组。Moya提供了一个插件机制,使我们可以建立自己的插件类来做一些额外的事情。比如写Log,显示“菊花”等。抽离出Plugin层的目的,就是让Provider职责单一,满足开闭原则。把和自己网络无关的行为抽离。避免各种业务揉在一起不利于扩展。

先来看看第一个EndpointClosure

EndpointClosure

//Moya.swift public typealias EndpointClosure = Target -> Endpoint<Target>
  • 1
  • 2
  • 3
  • 1
  • 2
  • 3

EndpointClosure这个闭包,输入是一个Target,返回Endpoint。这就是我们前面说的Target -> Endpoint的转换,那么Endpoint是个什么鬼? 
Endpoint 是Moya最终进行网络请求前的一种数据结构,它保存了这些数据:

  • URL
  • HTTP请求方式 (GET, POST, etc).
  • 本次请求的参数
  • 参数的编码方式 (URL, JSON, custom, etc).
  • stub数据的 response(测试用的)
//Endpoint.swift public class Endpoint<Target> { public typealias SampleResponseClosure = () -> EndpointSampleResponse public let URL: String public let method: Moya.Method public let sampleResponseClosure: SampleResponseClosure public let parameters: [String: AnyObject]? public let parameterEncoding: Moya.ParameterEncoding ... }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

Moya提供一个默认EndpointClosure的函数,来实现这个Target到Endpoint的转换:

//Moya.swift public final class func DefaultEndpointMapping(target: Target) -> Endpoint<Target> { let url = target.baseURL.URLByAppendingPathComponent(target.path).absoluteString return Endpoint(URL: url, sampleResponseClosure: {.NetworkResponse(200, target.sampleData)}, method: target.method, parameters: target.parameters) }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

上面的代码只是单纯地创建并返回一个Endpoint实例。然而在很多时候,我们需要自定义这个闭包来做更多额外的事情。后面在stub小节,你会看到,我们用stub模拟API请求失败的场景,给客户端返回一个非200的状态码。为了实现这个功能,在这个闭包里处理相关的逻辑,再合适不过了!或者说这个闭包就是让我们根据业务需求定制网络请求的。

RequestClosure

//Moya.swift public typealias RequestClosure = (Endpoint<Target>, NSURLRequest -> Void) -> Void
  • 1
  • 2
  • 3
  • 1
  • 2
  • 3

RequestClosure这个闭包就是实现将Endpoint -> NSURLRequest,Moya也提供了一个默认实现:

//Moya.swift public final class func DefaultRequestMapping(endpoint: Endpoint<Target>, closure: NSURLRequest -> Void) { return closure(endpoint.urlRequest) }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 1
  • 2
  • 3
  • 4
  • 5

默认实现也只是简单地调用endpoint.urlRequest取得一个NSURLRequest实例。然后调用了closure。然而,你可以在这里修改这个请求Request, 事实上这也是Moya给你的最后的机会。举个例子, 你想禁用所有的cookie,并且设置超时时间等。那么你可以实现这样的闭包:

let requestClosure = { (endpoint: Endpoint<GitHub>, done: NSURLRequest -> Void) in //可以在这里修改request let request: NSMutableURLRequest = endpoint.urlRequest.mutableCopy() as NSMutableURLRequest request.HTTPShouldHandleCookies = false request.timeoutInterval = 20 done(request) } provider = MoyaProvider(requestClosure: requestClosure) 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

从上面可以清晰地看出,EndpointClosure 和 RequestClosure 实现了 Target -> Endpoint -> NSRequest的转换流

StubClosure

//Moya.swift public typealias StubClosure = Target -> Moya.StubBehavior
  • 1
  • 2
  • 3
  • 1
  • 2
  • 3

StubClosure这个闭包比较简单,返回一个StubBehavior的枚举值。它就是让你告诉Moya你是否使用Stub返回数据或者怎样使用Stub返回数据

//Moya.swift public enum StubBehavior { case Never //不使用Stub返回数据 case Immediate //立即使用Stub返回数据 case Delayed(seconds: NSTimeInterval) //一段时间间隔后使用Stub返回的数据 }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

Never表明不使用Stub来返回模拟的网络数据, Immediate表示马上返回Stub的数据, Delayed是在几秒后返回。Moya默认是不使用Stub来测试。

在Target那一节我们定义了一个AccountAPI, API中我们实现了接口sampleData, 这个属性是返回Stub数据的。

extension AccountAPI: TargetType { ... var sampleData: NSData { switch self { case .Login: return "{'code': 1,6'Token':'123455'}".dataUsingEncoding(NSUTF8StringEncoding)! case .Register(let userName, let passwd): return "找不到数据" } } } let endPointAction = { (target: TargetType) -> Endpoint<AccountAPI> in let url = target.baseURL.URLByAppendingPathComponent(target.path).absoluteString switch target { case .Login: return Endpoint(URL: url, sampleResponseClosure: {.NetworkResponse(200, target.sampleData)}, method: target.method, parameters: target.parameters) case .Register: return Endpoint(URL: url, sampleResponseClosure: {.NetworkResponse(404, target.sampleData)}, method: target.method, parameters: target.parameters) } } let stubAction: (type: AccountAPI) -> Moya.StubBehavior = { type in switch type { case .Login: return Moya.StubBehavior.Immediate case .Register: return Moya.StubBehavior.Delayed(seconds: 3) } } let loginAPIProvider = MoyaProvider<AccountAPI>( endpointClosure: endPointAction, stubClosure: stubAction ) self.netProvider = loginAPIProvider loginAPIProvider.request(AccountAPI.Login(userName: "user", passwd: "123456")) { (result) in switch result { case .Success(let respones) : print(respones) case .Failure(_) : print("We got an error") } print(result) } 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48

就这样我们就实现了一个Stub! Login和Register都使用了Stub返回的数据。

注意:Moya中Provider对象在销毁的时候会去Cancel网络请求。为了得到正确的结果,你必须保证在网络请求的时候你的Provider不会被释放。否者你会得到下面的错误 “But don’t forget to keep a reference for it in property. If it gets deallocated you’ll see -999 “cancelled” error on response” 。通常为了避免这种情况,你可以将Provider实例设置为类成员变量,或者shared实例

Moya中Stub的实现

大多iOS的Http的Stub框架本质都是实现一个HTTP网络请求的代理类,去Hook系统Http请求。 如OHHTTPStub就是这么做的。在iOS中,HTTP代理类需要继承NSURLProtocol类,重载一些父类的方法,然后将这个代理类注册到系统中去。

class MyHttpProxy : NSURLProtocol { //重载一些父类的方法 override class func canInitWithRequest(request: NSURLRequest) -> Bool { return true } override class func canonicalRequestForRequest(request: NSURLRequest) -> NSURLRequest { return super.canonicalRequestForRequest(request) } .... } //注册 NSURLProtocol.registerClass(MyHttpProxy.self) 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

之后我们APP中所有的网络请求,都会去经过我们MyHttpProxy的代理类。 
然而Moya的Stub不是这样的,Moya的Stub的实现原理也超级无敌简单!它不是系统级别的,非入侵式的。它只是简单的加了一个判断而已!还是在Moya的Request方法里面

//Moya.swift public func request(target: Target, queue:dispatch_queue_t?, completion: Moya.Completion) -> Cancellable { let endpoint = self.endpoint(target) let stubBehavior = self.stubClosure(target) var cancellableToken = CancellableWrapper() let performNetworking = { (request: NSURLRequest) in if cancellableToken.isCancelled { return } switch stubBehavior { case .Never: cancellableToken.innerCancellable = self.sendRequest(target, request: request, queue: queue, completion: completion) default: cancellableToken.innerCancellable = self.stubRequest(target, request: request, completion: completion, endpoint: endpoint, stubBehavior: stubBehavior) } } requestClosure(endpoint, performNetworking) return cancellableToken }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22

Moya先调用我们在构造函数中传入的stubClosure闭包,如果stubBehavior是Never就真正的发起网络请求,否 
者就调用self.stubRequest

//Moya.swift

internal func stubRequest(target: Target, request: NSURLRequest, completion: Moya.Completion, endpoint: Endpoint<Target>, stubBehavior: Moya.StubBehavior) -> CancellableToken {
        ... let stub: () -> () = createStubFunction(cancellableToken, forTarget: target, withCompletion: completion, endpoint: endpoint, plugins: plugins) switch stubBehavior { case .Immediate: stub() case .Delayed(let delay): let killTimeOffset = Int64(CDouble(delay) * CDouble(NSEC_PER_SEC)) let killTime = dispatch_time(DISPATCH_TIME_NOW, killTimeOffset) dispatch_after(killTime, dispatch_get_main_queue()) { stub() } case .Never: fatalError("Method called to stub request when stubbing is disabled.") } ... }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

如果Immediate,就马上调用stub返回,是Delayed的话就Dispatch after延迟调用。

Manager

我们知道,Moya并不是一个网络请求的三方库,它只是一个抽象的网络层。它对其他网络库的进行了桥接,真正进行网络请求是别人的网络库(比如默认的Alamofire.Manager) 
为了达到这个目的Moya做了几件事情:

首先抽象了一个RequestType协议,利用这个协议将Alamofire隐藏了起来,让Provider类依赖于这个协议,而不是具体细节。

//Plugin.swift public protocol RequestType { var request: NSURLRequest? { get } func authenticate(user user: String, password: String, persistence: NSURLCredentialPersistence) -> Self func authenticate(usingCredential credential: NSURLCredential) -> Self }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

然后让Moya.Manager == Alamofire.Manager,并且让Alamofire.Manager也实现RequestType协议

Moya+Alamofire.swift

public typealias Manager = Alamofire.Manager /// Choice of parameter encoding. public typealias ParameterEncoding = Alamofire.ParameterEncoding //让Alamofire.Manager也实现 RequestType协议 extension Request: RequestType { }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

上面几步,就完成了Alamofire的封装、桥接。正因为桥接封装了Alamofire, 因此Moya的request,最终一定会调用Alamofire的request。简单的跟踪下Moya的Request方法就可以发现sendRequest调用了Alamofire。

//Moya.swift

func sendRequest(target: Target, request: NSURLRequest, queue: dispatch_queue_t?, completion: Moya.Completion) -> CancellableToken {
    //调用Alamofire发起网络请求
    let alamoRequest = manager.request(request)
        ... } 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

如果你想自定义你自己的Manager, 你可以传入你自己的Manager到Privoder。之后所有的请求都会经过你的这个Manager

let policies: [String: ServerTrustPolicy] = [ "example.com": .PinPublicKeys( publicKeys: ServerTrustPolicy.publicKeysInBundle(), validateCertificateChain: true, validateHost: true ) ] let manager = Manager( configuration: NSURLSessionConfiguration.defaultSessionConfiguration(), serverTrustPolicyManager: ServerTrustPolicyManager(policies: policies) ) let provider = MoyaProvider<MyTarget>(manager: manager)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

Plugin

Moya提供还提供插件机制,你可以自定义各种插件,所有插件必须满足PluginType协议

//Plugin.swift public protocol PluginType { /// Called immediately before a request is sent over the network (or stubbed). func willSendRequest(request: RequestType, target: TargetType) // Called after a response has been received, but before the MoyaProvider has invoked its completion handler. func didReceiveResponse(result: Result<Moya.Response, Moya.Error>, target: TargetType) }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

协议里只有两个方法,willSendRequest和didReceiveResponse。在进行网络请求之前和收到请求后,Moya会遍历所有的插件。分别去调用插件各自的willSendRequest和didReceiveResponse方法。

个人觉得这个插件更像是一个网络回调的Delegate,只是取了一个高大上的名字而已。不过将网络回调抽取出来确实能更好地将无关业务隔离,让Privoder更加专心的做自己的事情。而且以后也非常好扩展。

Moya默认提供了三个插件:

  • Authentication插件 (CredentialsPlugin.swift)。 HTTP认证的插件。
  • Logging插件(NetworkLoggerPlugin.swift)。在调试是,输入网络请求的调试信息到控制台
  • Network Activity Indicator插件(NetworkActivityPlugin.swift)。可以用这个插件来显示网络菊花

Network Activity Indicator插件用法示例,在网络进行请求开始请求时添加一个Spinner, 请求结束隐藏Spinner。这里用的是SwiftSpinner

let spinerPlugin = NetworkActivityPlugin { state in if state == .Began { SwiftSpinner.show("Connecting...") } else { SwiftSpinner.show("request finish...") SwiftSpinner.hide() } let loginAPIProvider = MoyaProvider<AccountAPI>( plugins: [spinerPlugin] ) loginAPIProvider.request(.Login) { _ in } 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

插件实现代码

插件的源码实现也超级简单。在进行网络请求之前和收到请求后,遍历所有的插件,调用其相关的接口。只是要分别处理下Stub和真正进行网络请求的两种情况

//Moya.swift func sendRequest(target: Target, request: NSURLRequest, queue: dispatch_queue_t?, completion: Moya.Completion) -> CancellableToken { let alamoRequest = manager.request(request) let plugins = self.plugins // 遍历插件,通知开始请求 plugins.forEach { $0.willSendRequest(alamoRequest, target: target) } // Perform the actual request alamoRequest.response(queue: queue) { (_, response: NSHTTPURLResponse?, data: NSData?, error: NSError?) -> () in let result = convertResponseToResult(response, data: data, error: error) // 遍历插件,通知收到请求 plugins.forEach { $0.didReceiveResponse(result, target: target) } completion(result: result) } alamoRequest.resume() return CancellableToken(request: alamoRequest) } //在测试时,Stub分支的也要,遍历调用一次插件 internal final func createStubFunction(token: CancellableToken, forTarget target: Target, withCompletion completion: Moya.Completion, endpoint: Endpoint<Target>, plugins: [PluginType]) -> (() -> ()) { return { if (token.canceled) { let error = Moya.Error.Underlying(NSError(domain: NSURLErrorDomain, code: NSURLErrorCancelled, userInfo: nil)) //调用插件 plugins.forEach { $0.didReceiveResponse(.Failure(error), target: target) } completion(result: .Failure(error)) return } switch endpoint.sampleResponseClosure() { case .NetworkResponse(let statusCode, let data): let response = Moya.Response(statusCode: statusCode, data: data, response: nil) //成功情况,调用插件 plugins.forEach { $0.didReceiveResponse(.Success(response), target: target) } completion(result: .Success(response)) case .NetworkError(let error): let error = Moya.Error.Underlying(error) //失败情况,调用插件 plugins.forEach { $0.didReceiveResponse(.Failure(error), target: target) } completion(result: .Failure(error)) } } } 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49

总结


总的来说Moya的实现比较简单,但是基于作者这种桥接、封装的思路,使得Moya扩展十分灵活,所以Moya有各种Provider, 能和RxSwift, RAC等等轻松的结合。 而Moya用起来也非常的干净。你不用关心Request具体实现。只用专注于你自己的Target设计就行。再加上Moya的Stub特性,的确使得它十分易于测试。

自己的思考


成也萧何败也萧何。然而我自己的感受,Moya让我们把所有的业务都放到Target中去,也会导致另外一些问题: 
(以下仅是个人观点,仅供参考)

  1. 枚举无法重载,代码未必简洁 
    比如,现在要添加一个新接口,还是要求实现Login功能,除了支持已有的用户名/密码登录,还要支持指纹登录。那么我们想定义可能想这样:Login(fingerPrint: String)。这两种登录情况实际上只是参数不一样。但在因为枚举中不能重载,所以为了添加这个case,我们不得不重新取一个名字,而不能利用函数重载。

    enum AccountAPI {
    case Login(userName: String, passwd: String) case Register(userName: String, passwd: String) //case Login(fingerPrint: String) //error: 不能这样添加错的,不支持重载 case LoginWithPrint(fingerPrint: String) //正确. 只能改名 } 
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    我个人觉得这样做,似乎并没有重载简洁。相比修改名字,我更喜欢重载。

  2. Target碎片化,后期维护困难 
    随着业务的增加,Target会变得很复杂。TargetType协议它是利用多个属性:method属性、parameters属性等。将一次API请求的实现的分割到多个了函数(属性)中去实现。这就导致实现碎片化了。添加一个API请求,你需要修改几个函数(属性), 改几个switch语句。如果文件很长,修改起来真的很烦,根本不好归类整理。

  3. 不利于多人协作开发 
    因为大家每次添加新功能,修改的都是这几个相同的函数(属性),所以非常容易导致文件冲突。

 
 
 
 
 
 
 

Endpoints

Endpoint是一种半私有的数据结构,Moya用来解释网络请求的根本构成。一个endpoint储存了以下数据:

  • The URL.
  • The HTTP method (GET,POST,等).
  • The request parameters.
  • The parameter encoding (URL,JSON,自定义,等).
  • The HTTP request header fields.
  • The sample response (单元测试用).

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

有两种方式使用Endpoints。

  1. 创建一个provider的时候,可以定义一个Target到Endpoint的映射。
  2. 创建一个provider的时候,可以定义一个Endpoint到 NSURLRequest的映射。

第一种方式如下:

let endpointClosure = { (target: MyTarget) -> Endpoint<MyTarget> in let url = target.baseURL.URLByAppendingPathComponent(target.path).absoluteString return Endpoint(URL: url, sampleResponseClosure: {.NetworkResponse(200, target.sampleData)}, method: target.method, parameters: target.parameters) }

这其实是Moya provides的默认实现。如果你需要自定义,比如你的API需要自定义参数mapping,或者在单元测试中创建一个返回非200 HTTP statuses的测试provider,可以在这里实现。

第二种方式很少使用。Moya希望使用者尽量不用关注底层实现的细节。但如果你需要, 请接着往下看。

让我们看看一个从Target到Endpoint的可变映射的示例。

From Target to Endpoint

默认情况,Endpoint 实例使用 .URL 类型的参数编码。如果需要其他编码方式,可以在配置provider时,用 Endpoint 的可选参数 parameterEncoding 来初始化你的endpointClosure

这里有四种编码类型:.URL.JSON.PropertyList 和.Custom,可以直接解析成Alamofire可用的类型。这些也可以在provider的 endpointClosure 中配置。通常你只会用到 .URL,但也可以使用任何你需要的。这是直接解析为 Alamofire parameter encodings

你可以在闭包里为HTTP头文件添加参数。例如,我们可能想要在HTTP头文件中设置”APP_NAME”以便服务器端解析。

let endpointClosure = { (target: MyTarget) -> Endpoint<MyTarget> in let url = target.baseURL.URLByAppendingPathComponent(target.path).absoluteString let endpoint: Endpoint<MyTarget> = Endpoint<MyTarget>(URL: url, sampleResponseClosure: {.NetworkResponse(200, target.sampleData)}, method: target.method, parameters: target.parameters) return endpoint.endpointByAddingHTTPHeaderFields(["APP_NAME": "MY_AWESOME_APP"]) }

这也意味着你可以为部分或全部endpoints提供附加参数。例如,我们需要给所有MyTarget 类型的 target添加认证token,但不包括用来进行认证的target。这就需要构造一个如下的 endpointClosure

let endpointClosure = { (target: MyTarget) -> Endpoint<MyTarget> in let url = target.baseURL.URLByAppendingPathComponent(target.path).absoluteString let endpoint: Endpoint<MyTarget> = Endpoint<MyTarget>(URL: url, sampleResponseClosure: {.NetworkResponse(200, target.sampleData)}, method: target.method, parameters: target.parameters) // Sign all non-authenticating requests switch target { case .Authenticate: return endpoint default: return endpoint.endpointByAddingHTTPHeaderFields(["AUTHENTICATION_TOKEN": GlobalAppStorage.authToken]) } }

注:我们可以在Moya现有的方法上进行扩展,endpointByAddingParameters 和 endpointByAddingHTTPHeaderFields 允许你利用Moya现有的代码添加自定义value。

Sample responses是 TargetType 协议所必须的。然而,这只是定义了返回数据。Target-to-Endpoint映射闭包可以定义更多细节,在单元测试时非常有用。

Sample responses返回下面二者之一:

  • NetworkResponse,包含一个 Int 类型的status code 和NSData 类型的返回数据。
  • NetworkError,包含一个 NSError? 类型的error。

Request Mapping

我们最初就提到,这个库不是一个封装网络请求的第三方库 – 那是Alamofire干的事。事实上,Moya是一种封装网络访问的方式,并提供编译时检查已经定义的targets。你已经知道怎样在MoyaProvider 的初始化中用 endpointClosure 把targets映射为endpoints。Moya会根据你创建的 Endpoint 实例来推动API请求。某些情况,Endpoint 必须要解析为 NSURLRequest 提供给Alamofire,这也就是 requestClosure 的作用。

requestClosure 是可选的,是修改request的根本办法。MoyaProvider.DefaultRequestMapper 使用 Endpoint 的 urlRequest 属性作为默认值。

这个闭包接受一个 Endpoint 作为参数,以及一个NSURLRequest -> Void ,在这里可以完成OAuth认证或其他事情。你想异步的调用这个闭包的时候,也可以使用第三方库认证 (example)。不需要修改request的话,可以单纯的log。

let requestClosure = { (endpoint: Endpoint<GitHub>, done: NSURLRequest -> Void) in let request = endpoint.urlRequest // Modify the request however you like. done(request) } provider = MoyaProvider<GitHub>(requestClosure: requestClosure)

requestClosure 在修改 NSURLRequest 属性时很好用,或者提供一些请求创建之前不知道的信息,例如cookies设置。请注意前面提到的 endpointClosure 不能实现这种应用层的特定请求。

这个属性对于修改request对象非常有用,NSURLRequest 可以自定义很多属性,例如你想让禁用所有cookies:

{ (endpoint: Endpoint<ArtsyAPI>) -> (NSURLRequest) in let request: NSMutableURLRequest = endpoint.urlRequest.mutableCopy() as NSMutableURLRequest request.HTTPShouldHandleCookies = false return request }

你也在请求送达之前调用这个闭包来可以打印网络请求。

 
 

RxSwift

Maya提供了一个可选的MoyaProvider 子类 – RxMoyaProvider。在网络请求完成时,我们不再使用 request() 函数的回调闭包,而是使用 Observable

RxMoyaProvider 可以像 MoyaProvider 一样创建和使用:

let provider = RxMoyaProvider<GitHub>()

然后,你就可以干很多事情:

provider.request(.Zen).subscribe { (event) -> Void in switch event { case .Next(let response): // do something with the data case .Error(let error): // handle the error default: break } }

对于 RxMoyaProvider,在请求被订阅前,不会发起网络请求。如果网络请求完成之前,订阅的信号被销毁了,请求将被取消。

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

  1. observable 发送一个 Moya.Response 类型的值 。
  2. observable 结束.

如果请求发生了错误(通常是NSURLSession错误),错误码是网络请求失败的status code,如果有的话,和response data,如果也有的话。

Moya.Response 类型包含一个 statusCode ,一些 data,和一个可以为空的 NSURLResponse。不管你习惯 subscribeNext 还是 map ,都可以使用这些数据。

更棒的是,Moya提供了一些Observable 的扩展,让你更简单的处理 MoyaResponses

  • filterStatusCodes() 提供了一系列status code。如果返回的status code不在其中,将生成一个error。
  • filterStatusCode() 用于查找特殊的status cod,如果没有找到,将生成error。
  • filterSuccessfulStatusCodes() 筛选200系列的status codes。
  • filterSuccessfulStatusAndRedirectCodes() 筛选200-300系列的status codes。
  • mapImage() 尝试将返回数据转换成 UIImage,失败的话将生成error。
  • mapJSON() 尝试将返回数据转换成 JSON对象,失败的话将生成error。
  • mapString()尝试将返回数据转换成字符串,失败的话将生成error。

在网络请求错误的情况下,error的 domain 是 MoyaErrorDomain。 通常code是 MoyaErrorCode 的rawValue。底层的errors提供了原始的返回数据,在 NSError 的 userInfo 中,关键字为”data”。

 
 
参考链接:
1.http://blog.csdn.net/lh844386434/article/details/51818017
2.http://www.cnblogs.com/liuliuliu/p/5627944.html
3.http://www.cnblogs.com/liuliuliu/p/5624026.html
4.http://www.cnblogs.com/liuliuliu/p/5626788.html
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/210058.html原文链接:https://javaforall.cn

【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛

【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...

(0)


相关推荐

  • ICMP报文格式解析

    ICMP报文格式解析ICMP报文的格式类型总共分为三大类:1、差错报文2、控制报文3、查询报文上图是ICMP报文的基本格式,上面提到的三种ICMP报文均有“类型,代码和校验和”三个字段,后面还有4个字节是根据不同的报文类型而有不同的格式,有的是全0,有的则有其他的特殊格式。但是ICMP始终有8个字节的头部长度。其中类型字段代表着不同的报文类型,而代码字段指明了某个类型的报文中细分出的该报文的指定的功能。即一个类型的报文拥有着多种功能。同时还需要注意的是ICMP差错报文的数据部分存储的是IP头部和IP头.

  • vim设置(非常全面),即.vimrc文件的配置

    vim设置(非常全面),即.vimrc文件的配置1.在终端下使用vim进行编辑时,默认情况下,编辑的界面上是没有显示行号、语法高亮度显示、智能缩进等功能的。为了更好的在vim下进行工作,需要手动设置一个配置文件:.vimrc。在启动vim时,当前用户根目录下的.vimrc文件会被自动读取,该文件可以包含一些设置甚至脚本,所以,一般情况下把.vimrc文件创建在当前用户的根目录下比较方便,即创建的命令为:$vi~/.vimrc

  • window批处理bat命令详解_cmd批处理命令

    window批处理bat命令详解_cmd批处理命令常见问题:1.如果你自己编写的.bat文件,双击打开,出现闪退 2.批处理.bat文件中输出中文乱码 解决方法在文章末尾!前言批处理文件(batchfile)包含一系列DOS命令,通常用于自动执行重复性任务。用户只需双击批处理文件便可执行任务,而无需重复输入相同指令。编写批处理文件非常简单,但难点在于确保一切按顺序执行。编写严谨的批处理文件可以极大程度地节省时间,在应对重复性工…

  • luajit vs php7,Luajit编译

    luajit vs php7,Luajit编译2、找到VC编译命令行,以VS2019为例,分别位于C:\ProgramData\Microsoft\Windows\StartMenu\Programs\VisualStudio019\VisualStudioTools\VC\x64NativeToolsCommandPromptforVS2019C:\ProgramData\Microsoft\Windows\Star…

  • byte与word的区别_女生类型分类

    byte与word的区别_女生类型分类在Visual C++ 6.0中,BYTE与WORD,DWORD本质上都是一种无符号整型,它们在WINDEF.H中被定义,定义如下:typedef unsigned char BYTE;typedef unsigned short WORD;typedef unsigned long DWORD;也就是说BYTE是无符号的char型(char型本质上也是一…

  • [java] java全局变量 声明和定义[通俗易懂]

    [java] java全局变量 声明和定义[通俗易懂]参考:https://blog.csdn.net/lilil371324/article/details/51241580JAVA全局变量:静态变量,实例变量(即在类体中定义的变量),静态变量:staticinta=3;实例变量:intb=5全局变量不能在类体中先声明(定义)后赋值但静态变量可以先在类体中声明,然后在方法中赋值(当然实例变量是不行的)publicclassTest{staticinta;//在类体中声明整型静态变量a。

发表回复

您的电子邮箱地址不会被公开。

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