Moya 代码阅读

第三方框架阅读的文章千千万,写下文章的目的一个是为了督促本身完整的学习,另外一个目的是输出倒逼本身思考git

Moya 是在 Alamofire 基础上的一个网络抽象层。简单的介绍能够移步对应的 Github 地址github

POP

由于库的总体结构是根据 POP 面向协议编程的思想,因此先来讲几句 POP。编程

Protocol

所谓协议,就是一组属性和/或方法的定义,而若是某个具体类型想要遵照一个协议,那它须要实现这个协议所定义的全部这些内容。协议实际上作的事情不过是“关于实现的约定”。swift

在喵神的博客中,由两片关于协议的应用,贴出来原文的地址api

面向协议编程与 Cocoa 的邂逅 (上)数组

面向协议编程与 Cocoa 的邂逅 (下)网络

TargetType

最基础使用的协议,用 class / struct/enum 实现均可以。闭包

利用枚举的特性就能够比较清晰的管理,或者也能够class/struct + enum 的形式管理app

enum NormalRequest {
	case post(String)
}
复制代码

validationType 属性,是对 Alamofire 返回的结果根据 statusCode 验证,也就是 Alamofire 自动化验证功能框架

extension NormalRequest: TargetType { ... 
	var validationType: ValidationType {
        return .successAndRedirectCodes
    }
}
复制代码

MoyaProvider

MoyaProvider 就是 Moya 最顶层的请求头。简单的设置了 target 以后,初始化一个对应 target 的 provider,不须要额外的参数

let provider = MoyaProvider<NormalRequest>()
复制代码

可是直接从 init 方法开始看,会发现能够设置 7 个参数,并且都给了默认实现

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
    }
复制代码

MoyaProvider+Defaults 文件里,一共只有三个方法,都是 MoyaProvider 的扩展里 。分别对应的就是 init 方法中的 endpointClosure ,requestClosure,manager 三者的默认实现方法。

endpointClosure

这里是从 Target → Endpoint 的映射,或者叫实现。实现代码

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
        )
    }
复制代码

Endpoint 还有两个实例方法,用来修改请求头数据和参数类型,返回的都是新的一个 Endpoint 对象

open func adding(newHTTPHeaderFields: [String: String]) -> Endpoint { ... }
open func replacing(task: Task) -> Endpoint { ... }
复制代码

requestClosure

从 Endpoint → URLRequest ,根据 Endpoint 的数据,生成一个 URLRequest

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)))
        }
    }
复制代码

Manager

这里的默认实现,是一个有着基本配置的 Alamofire.Session 对象。

对于 startRequestsImmediately ,文档中有这么解释

There is only one particular thing: since construct an Alamofire.Request in AF will fire the request immediately by default, even when "stubbing" the requests for unit testing. Therefore in Moya, startRequestsImmediately is set to false by default.

也就是以前每次构造出一个 request以后,默认状况下就是马上执行。因此在 Moya 中设置为 false

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

        let manager = Manager(configuration: configuration)
        manager.startRequestsImmediately = false
        return manager
    }
复制代码

Plugins

插件,Moya 一个十分特点的特性。对应的协议是 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>
复制代码

各个方法调用的时机

发送时机

Stub

在 target 中的 sampleData,能够是本身提供测试数据,用来不经过实际网络请求来模拟返回的数据结果。在 provider 初始化中,stubClosure 闭包是用来设置是否须要模拟测试

  • .never
  • .immediate
  • .delayed(seconds: TimeInterval)

在 Moya 中对于数据测试还有更多的选择 sampleResponseClosure

  • .networkResponse(Int, Data)
  • .response(HTTPURLResponse, Data)
  • .networkError(NSError) 模拟各类错误

那么 stub 分别能够在三个地方设置本身的配置

// 1. 设置测试数据
public var sampleData: Data {
    switch self {
    case .userRepositories(let name):
        return "[{\"name\": \"Repo Name\"}]".data(using: String.Encoding.utf8)!
    }
}
// 是否须要,且如何响应测试数据
let stubbingProvider = MoyaProvider<GitHub>(stubClosure: MoyaProvider.immediatelyStub)

// 以怎么样的响应数据返回
let customEndpointClosure = { (target: APIService) -> Endpoint in
    return Endpoint(url: URL(target: target).absoluteString,
                    sampleResponseClosure: { .networkResponse(401 , /* data relevant to the auth error */) },
                    method: target.method,
                    task: target.task,
                    httpHeaderFields: target.headers)
}

let stubbingProvider = MoyaProvider<GitHub>(endpointClosure: customEndpointClosure, stubClosure: MoyaProvider.immediatelyStub)
复制代码

发送请求

provider.request(.post("")){(result) in 
	swtich result {
	case let .success(response):
		break
	case let .fail(_):
		break
	}
}
复制代码

在 Moya 中 request 方法是一个统一的请求入口。只须要在方法中配置须要的参数,包括须要对应生成的请求地址,请求参数等经过枚举类型,十分清晰的分类和管理。利用 . 语法生成对应的枚举,而后依次生成 endpoint,URLRequest等。

方法中就能够发现,在这个方法里,还能够再次修改 callbackQueue

@discardableResult
    open func request(_ target: Target, callbackQueue: DispatchQueue? = .none, progress: ProgressBlock? = .none, completion: @escaping Completion) -> Cancellable {

    let callbackQueue = callbackQueue ?? self.callbackQueue
    return requestNormal(target, callbackQueue: callbackQueue, progress: progress, completion: completion)
}

复制代码

这个方法,作了一些生成请求时的通用处理。

在 init 方法中,不只能够传入对应的参数,还有一个目的是持有这些属性。

func requestNormal(_ target: Target, callbackQueue: DispatchQueue?, progress: Moya.ProgressBlock?, completion: @escaping Moya.Completion) -> Cancellable {}
复制代码

具体实现

方法的一开始,定义了三个常量

let endpoint = self.endpoint(target)
let stubBehavior = self.stubClosure(target)
let cancellableToken = CancellableWrapper()
复制代码

由于 init 时参数都是默认实现,因此第一句代码生成 endpoint 用的是 Moya 库里的实现方法。

这里会调用的是 defaultEndpointMapping 方法,返回 一个 Endpoint 对象

若是初始化时传入了本身定义的 endpoint 映射闭包,那么就会以自定义的方法执行

open func endpoint(_ token: Target) -> Endpoint {
      return endpointClosure(token)
}
复制代码

cancellableToken 是遵循 Cancellable 协议的类 CancellableWrapper 的对象。

这个协议就只有两行代码,是否已经取消的属性和取消请求的方法。并且这个方法在类中的实现就是将属性的值改成 true

/// A Boolean value stating whether a request is cancelled.
var isCancelled: Bool { get }

/// Cancels the represented request.
func cancel()
复制代码

这里就是插件 process 的执行位置,对每一个插件都执行一次。插件实现的 process 的方法对 result 的全部改动,最后都经过 completion 返回。

let pluginsWithCompletion: Moya.Completion = { result in
	let processedResult = self.plugins.reduce(result) { $1.process($0, target: target) }
	completion(processedResult)
}
复制代码

字面上理解,就是真正执行请求的下一步了。这个闭包,是在 endpoint → URLRequest 方法执行完成后的闭包

let performNetworking = { (requestResult: Result<URLRequest, MoyaError>) in
	// 先判断这个请求是否取消,是则返回错误类型为 cancel 的错误提示数据
    if cancellableToken.isCancelled {
    self.cancelCompletion(pluginsWithCompletion, target: target)
        return
    }

    var request: URLRequest!

    switch requestResult {
    case .success(let urlRequest):
        request = urlRequest
    case .failure(let error):
        pluginsWithCompletion(.failure(error))
        return
    }

    // Allow plugins to modify request
	// 插件执行 prepare
    let preparedRequest = self.plugins.reduce(request) { $1.prepare($0, target: target) }
	// 定义返回结果闭包,这里返回的是请求返回的数据映射成了 Result
    let networkCompletion: Moya.Completion = { result in
        if self.trackInflights {
          ....
        } else {
		// 使用上面的闭包,通知全部插件,且返回结果
        pluginsWithCompletion(result)
        }
    }
		// 这一步就是执行请求的下一步了,将全部参数继续传递
    cancellableToken.innerCancellable = self.performRequest(target, request: preparedRequest, callbackQueue: callbackQueue, progress: progress, completion: networkCompletion, endpoint: endpoint, stubBehavior: stubBehavior)
}
复制代码

接下去的就是将上面定义好的两个闭包,传入到 requestClosure 闭包中

endpoint 生成 URLRequest,而后请求完成以后执行 performNetworking 闭包内的代码

requestClosure(endpoint, performNetworking)
复制代码

在这个方法中,有两段根据 trackInflights 属性的代码。

查阅一些资料以后,发现不多说起这个属性的解释,根据代码逻辑能够看出来,这是是否对重复请求状况的处理。其中有一个解释是:是否要跟踪重复网络请求

#229 中,提到这个特性是指 Moya 一开始跟踪 API 请求,而后作了防止重复请求的处理。问题提出,在有些时间咱们是须要重复请求一个 API 的,因此当时的作法是,#232 删除了 防止重复请求的代码。#477 中,才有了如今的这一版,对重复 API 的请求利用字典管理。

if trackInflights {
    objc_sync_enter(self)
    var inflightCompletionBlocks = self.inflightRequests[endpoint]
    inflightCompletionBlocks?.append(pluginsWithCompletion)
    self.inflightRequests[endpoint] = inflightCompletionBlocks
    objc_sync_exit(self)

    if inflightCompletionBlocks != nil {
	    // 若是存在,就是说明已经有一个已经重复的请求了
        return cancellableToken
    } else {
		// 若是不存在 key 为 endpoint 的值,则初始化一个
        objc_sync_enter(self)
        self.inflightRequests[endpoint] = [pluginsWithCompletion]
        objc_sync_exit(self)
    }
}
	....

	let networkCompletion: Moya.Completion = { result in
        if self.trackInflights {
            self.inflightRequests[endpoint]?.forEach { $0(result) }

            objc_sync_enter(self)
            self.inflightRequests.removeValue(forKey: endpoint)
            objc_sync_exit(self)
        } else {
            pluginsWithCompletion(result)
        }
    }
复制代码

一个请求在 init 的时候将 trackInflights 设置为 true,那么在 Moya 中就会存储这个请求的 endpoint。在返回数据的时候,若是须要跟踪了重复请求,那么就将一次实际发送请求返回的数据,屡次返回。

performRequest

cancellableToken.innerCancellable = 
	self.performRequest(target, 
	request: preparedRequest, // 插件执行 prepare 闭包返回 request
	callbackQueue: callbackQueue, 
	progress: progress, 
	completion: networkCompletion, // 网络请求成功以后,将结果返回的闭包
	endpoint: endpoint, 
	stubBehavior: stubBehavior)
复制代码

这个方法的内部实现,根据 switch stubBehavior 和 endpoint.task 来分别执行对应的请求方式。这里只贴除了最简单的一个

switch stubBehavior {
 case .never:
  switch endpoint.task {
    case .requestPlain, .requestData, .requestJSONEncodable, .requestCustomJSONEncodable, .requestParameters, .requestCompositeData, .requestCompositeParameters:
         return self.sendRequest(target, request: request, callbackQueue: callbackQueue, progress: progress, completion: completion)
	  ....
	}
	default:
		return self.stubRequest...
}
复制代码

通常请求的实现。在这一层,就是和 Alamofire 产生联系的一层。由于咱们在以前都已经生成了 urlRequest 。因此都是对应生成 DataRequest,DownloadRequest,UploadRequest

func sendRequest(_ target: Target, request: URLRequest, callbackQueue: DispatchQueue?, progress: Moya.ProgressBlock?, completion: @escaping Moya.Completion) -> CancellableToken {
	let initialRequest = manager.request(request as URLRequestConvertible)
  let validationCodes = target.validationType.statusCodes
  let alamoRequest = validationCodes.isEmpty ? initialRequest : initialRequest.validate(statusCode: validationCodes)
  return sendAlamofireRequest(alamoRequest, target: target, callbackQueue: callbackQueue, progress: progress, completion: completion)
}
复制代码

参考连接

如何更深刻使用Moya Moya的设计之道

相关文章
相关标签/搜索