美文网首页iOS 坑的集中营
Swift-Moya 源码解析

Swift-Moya 源码解析

作者: iOS_磊 | 来源:发表于2020-12-03 13:39 被阅读0次

    1.Moya的定义

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

    2.Moya的使用

    • Moya的使用分成几步,首先需要先自定义一个枚举类型。
    enum SHChannelViewModelApiManager{
        case getChannelList(Bool)
        case getItemList(String)
    
    }
    
    • 然后遵循开闭原则,让枚举的分类遵循MoyaTargetType,按需实现定义的各种get方法
    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 }
    
        /// A Boolean value determining whether the embedded target performs Alamofire validation. Defaults to `false`.
        var validate: Bool { get }
    
        /// The headers to be used in the request.
        var headers: [String: String]? { get }
    }
    
    extension  SHChannelViewModelApiManager:TargetType {
       var baseURL: URL {
            return URL(string: baseUrl)!
        }
        var task: Task {
            switch self {
            case .getChannelList:
                return .requestPlain
            case .getItemList(let pipe):
                return .requestParameters(parameters: ["pipe":pipe], encoding: URLEncoding.queryString)
            }
        }
        var method: Moya.Method {
            return .get
        }
        var path: String {
            switch self {
            case .getChannelList(let isToday):
                if isToday{
                   return "/Youmeng/chaxunservletall"
                }else{
                  return "/Youmeng/chaxunservletallhistory"
                }
            case .getItemList:
                return itemListUrl
    
            }
    
        }
    }
    
    • 最后创建MoyaProvider对象,泛型允许传入任何你定义的遵循TargetType协议的枚举,
        let provider = MoyaProvider<SHChannelViewModelApiManager>()
    
    
    • 使用MoyaProvider对象发起请求
        provider.rx.request(.getItemList(pipe)).mapArr([SHChannelItemTopModel].self).subscribe(onSuccess: { [weak self](model) in
            self?.topModels = model
            self?.itemOutput.onNext(true)
            }) { [weak self](error) in
                self?.itemOutput.onNext(false)
                }.disposed(by: bag)
        }
    

    3.Moya的所有文件解析

    • Provider

    • TargetType

      • TargetType

      • MultiTarget (MultiTarget用于使MoyaProvider能够处理多个TargetType

    • Result

      • Response

      • MoyaError (是一个枚举,定义了 Moya可能抛出的各种错误,包括上面说的 Response三个map方法出错,状态码出错,解码出错,和参数编码错误等,同时有两个get方法,得出错误的Response返回以及错误描述返回)

    • Plugins

    • Alamofire
      这里有一个iOS交流圈:891 488 181 分享BAT,阿里面试题、面试经验,讨论技术, 感兴趣的话大家一起交流学习!

      • Moya+Alamofire (MoyaAlamofire的桥接文件,桥接模式保证了最少知道原则,如果进行替换Alamofire,主要修改这个文件)
      • MultipartFormData (多文件上传与Alamofire的桥接,通过 append等方法把moya形式的MultipartFormData添加到Alamofire里去)
    • URL

      • URL+Moya(通过获取TargetTypebaseURLpath初始化一个URL)

      • URLRequest+Encoding(对URLRequest的两个encoded方法)

    3.1.MoyaProvider

    MoyaProvider 初始化

    MoyaProvider是请求提供者类。只能通过该类发起请求,类的初始化如下

        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
        }
    

    由以上代码可以得知,初始化可传入的参数。

    • EndpointClosure是一个把传入的Target转化为Endpoint对象的闭包,

          public typealias EndpointClosure = (Target) -> Endpoint<Target>
      
      

      既然如此,我们就顺腾摸瓜,看一下Endpoint对象里面是什么

      open class Endpoint<Target> {
       public typealias SampleResponseClosure = () -> EndpointSampleResponse
      
       /// 请求的URL的字符串.
       open let url: String
      
       /// stub数据的 response(测试用的)
       open let sampleResponseClosure: SampleResponseClosure
      
       /// 请求方式.
       open let method: Moya.Method
      
       /// 请求任务.
       open let task: Task
      
       /// 请求头.
       open let httpHeaderFields: [String: String]?
      
       public init(url: String,
                   sampleResponseClosure: @escaping SampleResponseClosure,
                   method: Moya.Method,
                   task: Task,
                   httpHeaderFields: [String: String]?) {
      
           self.url = url
           self.sampleResponseClosure = sampleResponseClosure
           self.method = method
           self.task = task
           self.httpHeaderFields = httpHeaderFields
       }
       ...
      }
      

      可以看出,Endpoint的属性,基本对应TargetType协议对应的get方法,所以才能进行转化,EndpointClosure的作用在于,可以根据业务需求在这里重新定制网络请求,还可以通过 stub进行数据测试,可以看看官方默认的闭包实现

       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
           )
       }
      
    • RequestClosure这个闭包实现就是将Endpoint转化成真正的请求对象URLRequest

      //RequestClosure
          public typealias RequestClosure = (Endpoint<Target>, @escaping RequestResultClosure) -> Void
       // 上面的RequestResultClosure
           public typealias RequestResultClosure = (Result<URLRequest, MoyaError>) -> Void
      
      

      看看Moya提供的默认实现

          public final class func defaultRequestMapping(for endpoint: Endpoint<Target>, closure: RequestResultClosure) {
           do {
              //urlRequest请求
               let urlRequest = try endpoint.urlRequest()
               closure(.success(urlRequest))
               // MoyaError类型的错误,稍后讲解
           } 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)))
           }
       }
      

      上面代码通过endpoint.urlRequest()获得urlRequest,那我们就看一下urlRequest()的具体实现

       public func urlRequest() throws -> URLRequest {
           guard let requestURL = Foundation.URL(string: url) else {
               throw MoyaError.requestMapping(url)
           }
      
           var request = URLRequest(url: requestURL)
           request.httpMethod = method.rawValue
           request.allHTTPHeaderFields = httpHeaderFields
      
           switch task {
           case .requestPlain, .uploadFile, .uploadMultipart, .downloadDestination:
               return request
           case .requestData(let data):
               request.httpBody = data
               return request
       //.................此处省略一点代码
           }
       }
      

      现在我们清晰的发现确实是转成URLRequest了,事实上,这也是Moya给你最后的机会了,举个例子,你想设置超时时间,

      let  requestClosure = { (endpoint:Endpoint<SHChannelViewModelApiManager>,closure:RequestResultClosure){
        do {
               var urlRequest = try endpoint.urlRequest()
               //设置超时时间,urlRequest的可配置的东西都可以在这配置
               urlRequest.timeoutInterval = 60
               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)))
           }
      }
      }
      
    • StubClosure返回了一个StubBehavior的枚举值,它就是让你告诉Moya你是否使用Stub返回数据或者怎样使用Stub返回数据,默认是不返回

       public typealias StubClosure = (Target) -> Moya.StubBehavior
      
      
      public enum StubBehavior {
      
       /// 不使用Stub返回数据.
       case never
      
       /// 立即使用Stub返回数据
       case immediate
      
       /// 一段时间间隔后使用Stub返回的数据.
       case delayed(seconds: TimeInterval)
      }
      
      

      下面用个例子来总结一下这三个闭包的用法

      var sampleData: Data {
      return "{'code': 0,'Token':'3764837egfdg8dfg8e93hr93'}".data(using:  String.Encoding.utf8)!
       }
      //定义在SHChannelViewModelApiManager外头 
      let endPointAction  = {(target: SHChannelViewModelApiManager) ->  Endpoint<SHChannelViewModelApiManager> in
        return Endpoint(
            url: URL(target: target).absoluteString,
            sampleResponseClosure: { .networkResponse(400, target.sampleData) },
            method: target.method,
            task: target.task,
            httpHeaderFields: target.headers
        )
      }
      //3秒后返回
      
      let stubAction: (_ type: SHChannelViewModelApiManager) ->  Moya.StubBehavior  = { type in
            return Moya.StubBehavior.delayed(seconds: 3)
      
      }
      //创建moyaProvider
        let moyaProvider = MoyaProvider<SHChannelViewModelApiManager>(endpointClosure: endPointAction,  stubClosure: stubAction)
       //使用
       moyaProvider.request(SHChannelViewModelApiManager.getChannelList)........
      
      
    • Manager 就没什么好说的了,就是Alamofire的SessionManager

      public typealias Manager = Alamofire.SessionManager
      
      

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

    • callbackQueue作为回调队列传给Alamofire,如果为nil - 将使用Alamofire默认的,下面是找到的一个用途例子

          if let callbackQueue = callbackQueue {
                    callbackQueue.async(execute: sendProgress)
                } else {
                    sendProgress()
                }
      
    • plugins- Moya提供了一个插件机制,使我们可以建立自己的插件类来做一些额外的事情。比如写Log,显示“菊花”等。抽离出Plugin层的目的,就是让Provider职责单一,满足开闭原则。把和自己网络无关的行为抽离。避免各种业务揉在一起不利于扩展,(其实更像一个请求的生命周期,在该插入的地方调用)

    • trackInflights ,根据代码逻辑可以看出来,这是是否对重复请求情况的处理。其中有一个解释是:是否要跟踪重复网络请求。

          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 {
                   objc_sync_enter(self)
                   // 如果不存在 key 为 endpoint 的值,则初始化一个
                   self.inflightRequests[endpoint] = [pluginsWithCompletion]
                   objc_sync_exit(self)
               }
           }
      

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

    MoyaProvider 发送请求
    • Moyarequest 方法是一个统一的请求入口。只需要在方法中配置需要的参数,包括需要对应生成的请求地址,请求参数等通过枚举类型,十分清晰的分类和管理。利用 . 语法生成对应的枚举,然后依次生成 endpointURLRequest

          @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)
        }
      
    • target就是传入的自定义枚举。

    • callbackQueue 同上

    • progress代表请求任务完成进度的回调,默认不使用

      public typealias ProgressBlock = (_ progress: ProgressResponse) -> Void
      
      

      再点进去ProgressResponse看看

      public struct ProgressResponse {
      
       /// The optional response of the request.
       public let response: Response?
      
       /// An object that conveys ongoing progress for a given request.
       public let progressObject: Progress?
      
       /// Initializes a `ProgressResponse`.
       public init(progress: Progress? = nil, response: Response? = nil) {
           self.progressObject = progress
           self.response = response
       }
      
       /// The fraction of the overall work completed by the progress object.
       public var progress: Double {
           return progressObject?.fractionCompleted ?? 1.0
       }
      
       /// A Boolean value stating whether the request is completed.
       public var completed: Bool {
           return progress == 1.0 && response != nil
       }
      }
      

      由上可知,progressObject是一个Foundation框架的Progress对象,这是 iOS 7加入的专门用于监控任务进度的类

    • completion是请求完成后返回的回调

      public typealias Completion = (_ result: Result<Moya.Response, MoyaError>) -> Void
      

    3.2.MoyaProvider+Defaults

    MoyaProvider+Defaults里面就是 3 个默认方法,前面已经提到过,就不多做赘述了

    • defaultEndpointMapping 返回 Endpoint的默认方法
    • defaultRequestMapping 本质是返回URLRequest的默认方法
    • defaultAlamofireManager返回网络库的manager的默认方法(默认是Alamofire)

    3.3.MoyaProvider+Internal

    • Method是对Alamofire.HTTPMethod的拓展,添加supportsMultipart方法来判断,请求方式支不支持多种请求方式一起出现

      extension Method {
       /// A Boolean value determining whether the request supports multipart.
       public var supportsMultipart: Bool {
           switch self {
           case .post, .put, .patch, .connect:
               return true
           case .get, .delete, .head, .options, .trace:
               return false
           }
       }
      }
      
    • requestNormalMoyaProviderrequest调的方法,方法里说明了,Moya在请求的时候到底做了什么

         func requestNormal(_ target: Target, callbackQueue: DispatchQueue?, progress: Moya.ProgressBlock?, completion: @escaping Moya.Completion) -> Cancellable {
         //获取endpoint、stubBehavior和初始化cancellableToken
           let endpoint = self.endpoint(target)
           let stubBehavior = self.stubClosure(target)
           //这个类控制是否取消请求任务
           let cancellableToken = CancellableWrapper()
      
           // 允许插件修改 response
           let pluginsWithCompletion: Moya.Completion = { result in
               let processedResult = self.plugins.reduce(result) { $1.process($0, target: target) }
               completion(processedResult)
           }
          //是否追踪重复请求
        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 {
                   objc_sync_enter(self)
                   // 如果不存在 key 为 endpoint 的值,则初始化一个
                   self.inflightRequests[endpoint] = [pluginsWithCompletion]
                   objc_sync_exit(self)
               }
           }
          //字面上理解,就是真正执行请求的下一步了。这个闭包,是在 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
               }
      
               // 允许插件修改 request
               let preparedRequest = self.plugins.reduce(request) { $1.prepare($0, target: target) }
             // 定义返回结果闭包,这里返回的是请求返回的数据映射成了 Result
               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)
                 }
               }
        // 这一步就是执行请求的下一步了,将所有参数继续传递
               cancellableToken.innerCancellable = self.performRequest(target, request: preparedRequest, callbackQueue: callbackQueue, progress: progress, completion: networkCompletion, endpoint: endpoint, stubBehavior: stubBehavior)
           }
        // 接下去的就是将上面定义好的两个闭包,传入到 requestClosure 闭包中
           requestClosure(endpoint, performNetworking)
      
           return cancellableToken
       }
      
      
    • performRequest是上面执行请求的下一步,这个方法的内部实现,根据 switch stubBehaviorendpoint.task 来分别执行对应的请求方式。

     private func performRequest(_ target: Target, request: URLRequest, callbackQueue: DispatchQueue?, progress: Moya.ProgressBlock?, completion: @escaping Moya.Completion, endpoint: Endpoint<Target>, stubBehavior: Moya.StubBehavior) -> Cancellable {
            switch stubBehavior {
            case .never:
                switch endpoint.task {
                case .requestPlain, .requestData, .requestJSONEncodable, .requestParameters, .requestCompositeData, .requestCompositeParameters:
                    return self.sendRequest(target, request: request, callbackQueue: callbackQueue, progress: progress, completion: completion)
                case .uploadFile(let file):
                    return self.sendUploadFile(target, request: request, callbackQueue: callbackQueue, file: file, progress: progress, completion: completion)
                case .uploadMultipart(let multipartBody), .uploadCompositeMultipart(let multipartBody, _):
                    guard !multipartBody.isEmpty && endpoint.method.supportsMultipart else {
                        fatalError("\(target) is not a multipart upload target.")
                    }
                    return self.sendUploadMultipart(target, request: request, callbackQueue: callbackQueue, multipartBody: multipartBody, progress: progress, completion: completion)
                case .downloadDestination(let destination), .downloadParameters(_, _, let destination):
                    return self.sendDownloadRequest(target, request: request, callbackQueue: callbackQueue, destination: destination, progress: progress, completion: completion)
                }
            default:
                return self.stubRequest(target, request: request, callbackQueue: callbackQueue, completion: completion, endpoint: endpoint, stubBehavior: stubBehavior)
            }
        }
    
    

    3.4.Endpoint

    • Endpoint的初始化方法

       public init(url: String,
                   sampleResponseClosure: @escaping SampleResponseClosure,
                   method: Moya.Method,
                   task: Task,
                   httpHeaderFields: [String: String]?) {
      
           self.url = url
           self.sampleResponseClosure = sampleResponseClosure
           self.method = method
           self.task = task
           self.httpHeaderFields = httpHeaderFields
      
    • adding用于创建一个新的Endpoint的便利方法,其属性与接收方相同,但增加了HTTP请求头。

      open func adding(newHTTPHeaderFields: [String: String]) -> Endpoint<Target> {
           return Endpoint(url: url, sampleResponseClosure: sampleResponseClosure, method: method, task: task, httpHeaderFields: add(httpHeaderFields: newHTTPHeaderFields))
       }
      
      
    • replacing方法差不多,只是更换了Task

    • urlRequest()方法 转换EndpointURLRequest

    3.5.TargetType

    TargetType就是用于定义MoyaProvider的协议,自定义枚举需要签订的协议

    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 }
    
       /// A Boolean value determining whether the embedded target performs Alamofire validation. Defaults to `false`.
       var validate: Bool { get }
    
       /// The headers to be used in the request.
       var headers: [String: String]? { get }
    }
    

    3.6.Response

    Response就是对对MoyaProvider.request的响应

    • Response的初始化

       public init(statusCode: Int, data: Data, request: URLRequest? = nil, response: HTTPURLResponse? = nil) {
           self.statusCode = statusCode //状态码
           self.data = data //返回的二进制数据
           self.request = request // URL 请求
           self.response = response// http 的 response
       }
      

      除此之外,还有自带的mapJSONmapString用于将data转成 JSON或者字符串,map<D: Decodable>自带转模型,可以讲数据转换为签订Decodable类的对象,mapImage在返回 data数据为一个图片二进制数据时使用,直接转换成图片对象返回

         func mapImage() throws -> Image {
           guard let image = Image(data: data) else {
               throw MoyaError.imageMapping(self)
           }
           return image
       }
      
         func mapJSON(failsOnEmptyData: Bool = true) throws -> Any {
           do {
               return try JSONSerialization.jsonObject(with: data, options: .allowFragments)
           } catch {
               if data.count < 1 && !failsOnEmptyData {
                   return NSNull()
               }
               throw MoyaError.jsonMapping(self)
           }
       }
      
           public func mapString(atKeyPath keyPath: String? = nil) throws -> String {
           if let keyPath = keyPath {
               // Key path was provided, try to parse string at key path
               guard let jsonDictionary = try mapJSON() as? NSDictionary,
                   let string = jsonDictionary.value(forKeyPath: keyPath) as? String else {
                       throw MoyaError.stringMapping(self)
               }
               return string
           } else {
               // Key path was not provided, parse entire response as string
               guard let string = String(data: data, encoding: .utf8) else {
                   throw MoyaError.stringMapping(self)
               }
               return string
           }
       }
      
         func map<D: Decodable>(_ type: D.Type, atKeyPath keyPath: String? = nil, using decoder: JSONDecoder = JSONDecoder()) throws -> D {
           let serializeToData: (Any) throws -> Data? = { (jsonObject) in
               guard JSONSerialization.isValidJSONObject(jsonObject) else {
                   return nil
               }
               do {
                   return try JSONSerialization.data(withJSONObject: jsonObject)
               } catch {
                   throw MoyaError.jsonMapping(self)
               }
           }
           let jsonData: Data
           if let keyPath = keyPath {
               guard let jsonObject = (try mapJSON() as? NSDictionary)?.value(forKeyPath: keyPath) else {
                   throw MoyaError.jsonMapping(self)
               }
      
               if let data = try serializeToData(jsonObject) {
                   jsonData = data
               } else {
                   let wrappedJsonObject = ["value": jsonObject]
                   let wrappedJsonData: Data
                   if let data = try serializeToData(wrappedJsonObject) {
                       wrappedJsonData = data
                   } else {
                       throw MoyaError.jsonMapping(self)
                   }
                   do {
                       return try decoder.decode(DecodableWrapper<D>.self, from: wrappedJsonData).value
                   } catch let error {
                       throw MoyaError.objectMapping(error, self)
                   }
               }
           } else {
               jsonData = data
           }
           do {
               return try decoder.decode(D.self, from: jsonData)
           } catch let error {
               throw MoyaError.objectMapping(error, self)
           }
       }
      
      

    3.7.Plugin

    Moya Plugin接收回调(调用时机都是在MoyaProvider+Internal里),以在发送或接收请求时执行。例如,一个插件可以用于

    1.记录网络请求
    
    2.隐藏和显示网络活动指示器
    
    3.在请求中注入附加信息
    
    • prepare可以用来修改发送前的请求。(在 stub 测试之前)

          func prepare(_ request: URLRequest, target: TargetType) -> URLRequest
      
    • willSend在网络请求发送前调用(在 stub 测试之后)

          func willSend(_ request: RequestType, target: TargetType)
      
    • didReceive 在收到响应后,但在MoyaProvider调用其完成处理程序之前调用。

          func didReceive(_ result: Result<Moya.Response, MoyaError>, target: TargetType)
      
    • processcompletion前调用用来修改result

          func process(_ result: Result<Moya.Response, MoyaError>, target: TargetType) -> Result<Moya.Response, MoyaError>
      
    • RequestTypewillSend需要传入的参数,它的设计遵循迪米特法则(最少知道),我们使用这个协议来代替Alamofire请求,以避免泄露这个抽象概念。Plugin应该是完全不知道Alamofire

      public protocol RequestType {
      
        /// 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
       }
      

    3.8.AccessTokenPlugin

    AccessTokenPlugin可用于做JWTBearer 认证 和 Basic 认证,也可以做OAuth认证,不过比较麻烦

    • 必要的时候通过prepare添加授权请求头来验证

          public func prepare(_ request: URLRequest, target: TargetType) -> URLRequest {
          guard let authorizable = target as? AccessTokenAuthorizable else { return request }
      
          let authorizationType = authorizable.authorizationType
      
          var request = request
      
          switch authorizationType {
          case .basic, .bearer:
          //添加Authorization,tokenClosure返回一个可应用请求头上的access token
              let authValue = authorizationType.rawValue + " " + tokenClosure()
              request.addValue(authValue, forHTTPHeaderField: "Authorization")
          case .none:
              break
          }
      
          return request
      }
      

    3.9.CredentialsPlugin

    AccessTokenPlugin是做 HTTP 身份验证的,在 willSend里验证

    
      public func willSend(_ request: RequestType, target: TargetType) {
      //credentialsClosure返回一个 URLCredential对象,用于身份验证的系统 api
          if let credentials = credentialsClosure(target) {
          //通过Moya+Alamofire的“extension Request: RequestType { }” 可知,这个authenticate方法最后还是调用的 Alamofire的的认证方法
              _ = request.authenticate(usingCredential: credentials)
          }
      }
    

    3.10.NetworkActivityPlugin

    NetworkActivityPlugin还是比较简单的,就是单纯的抽离willSenddidReceive转变成NetworkActivityChangeTypebeganended,可以添加菊花的显示和隐藏

        public func willSend(_ request: RequestType, target: TargetType) {
          networkActivityClosure(.began, target)
      }
    
      /// 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, target)
      }
    

    3.11.NetworkLoggerPlugin

    NetworkLoggerPlugin是网络日志的打印,还是在willSenddidReceive打印了网络状态

        public func willSend(_ request: RequestType, target: TargetType) {
         if let request = request as? CustomDebugStringConvertible, cURL {
             output(separator, terminator, request.debugDescription)
             return
         }
         outputItems(logNetworkRequest(request.request as URLRequest?))
     }
    
     public func didReceive(_ result: Result<Moya.Response, MoyaError>, target: TargetType) {
         if case .success(let response) = result {
             outputItems(logNetworkResponse(response.response, data: response.data, target: target))
         } else {
             outputItems(logNetworkResponse(nil, data: nil, target: target))
         }
     }
    

    作者:Mikebanana
    链接:https://juejin.cn/post/6899362830114357256

    相关文章

      网友评论

        本文标题:Swift-Moya 源码解析

        本文链接:https://www.haomeiwen.com/subject/gynywktx.html