Moya 的实现(二)

Monday, April 9, 2018

上篇文章我们讲了 Moya 的请求过程,那处理响应这块,Moya 是怎么处理的呢,这篇笔记就主要是研究这个。

处理响应

接收数据及回传

我们这里不去研究 Alamofire 的处理,就看看Moya 接收到响应数据后,怎么处理的。

我们在发起请求的位置可以看到接收响应数据的代码:

<code class="language-objectivec"><br></br>let completionHandler: RequestableCompletion = { response, request, data, error in
            let result = convertResponseToResult(response, request: request, data: data, error: error)
            // Inform all plugins about the response
            plugins.forEach { $0.didReceive(result, target: target) }
            progressCompletion?(ProgressResponse(response: result.value))
            completion(result)
        }

progressAlamoRequest = progressAlamoRequest.response(callbackQueue: callbackQueue, completionHandler: completionHandler)
progressAlamoRequest.resume()
</code>

我们可以看到 response 方法接收 completionHandle,在这里首先会把一个响应相关的信息丢到 convertResponseToResult() 方法里面做个包装,把对象封装到一个Response 对象里面,再打包到 Result 中。

public func convertResponseToResult(_ response: HTTPURLResponse?, request: URLRequest?, data: Data?, error: Swift.Error?) ->
    Result<Moya.Response, MoyaError> {
        switch (response, data, error) {
        case let (.some(response), data, .none):
            let response = Moya.Response(statusCode: response.statusCode, data: data ?? Data(), request: request, response: response)
            return .success(response)
        case let (_, _, .some(error)):
            let error = MoyaError.underlying(error)
            return .failure(error)
        default:
            let error = MoyaError.underlying(NSError(domain: NSURLErrorDomain, code: NSURLErrorUnknown, userInfo: nil))
            return .failure(error)
        }
}


    
    <code class="language-null"><br></br>我们可以看看 Response 对象:
    ```objectivec
    public final class Response: CustomDebugStringConvertible, Equatable {
    
        public let statusCode: Int      // 状态码
        public let data: Data       // 数据
        public let request: URLRequest? // 对应的请求对象
        public let response: HTTPURLResponse?   // 响应对象
        ......
    }
    </code>



 convertResponseToResult() 返回的 Result 会被传入 completion()回调中。

这个 completion 是从requestNormal()中传进来的


    
    <code class="language-objectivec">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)
        }
    }
    
    </code>



我们只关注 pluginsWithCompletion()


    
    <code class="language-objectivec">let pluginsWithCompletion: Moya.Completion = { result in
        let processedResult = self.plugins.reduce(result) { $1.process($0, target: target) }
        completion(processedResult)
    }
    </code>



这个 completion 就是由业务层代码传进来的回调了,最后数据流就回到了业务层。



### 转换数据



业务层接收到数据后,就可以直接使用数据。不过这里我们获取到的是一个 Response 对象,也就是说我们获取的基本上是一个没处理过的数据。

Moya 这么做的意义是:
    - Moya 只关注网络请求,能保持轻量
    - 不与具体数据转换库耦合,方便扩展,让用户决定怎么转换数据,同时减少依赖库。
    - 回传裸数据,让用户去定义接口的数据格式,方便扩展。

Moya 也提供了几个转换方法
- mapImage() 尝试把响应数据转换为 UIImage 实例
- mapJSON() 尝试把响应数据转换成一个字符串
- mapString(atKeyPath:)尝试把响应数据的 key Path 映射成一个字符串,如果不成功将产生一个错误。



### 测试插桩



Moya 还有一个很好的特性,就是为本地 mock 数据提供了一个很好的支持。

要想使用本地 mock 功能,可以在创建MoyaProvider 时传入 stubClosure 参数,值为 MoyaProvider.immediatelyStub 或者 MoyaProvider.delayedStub,其值被赋值给 MoyaProvider  stubClosure 属性。

流程图如下

[![](/uploads/2018/04/moya_stub-778x1024.png)
](/uploads/2018/04/moya_stub.png)




  *  performRequest() 中根据 stubBehavior 来判断,而进入 stubRequest() 方法
  *  stubRequest() 方法中,使用 createStubFunction 创建 stub() 闭包,并根据 stubBehavior 来决定 stub() 的执行方式和时机:
  * 最主要的操作是在 stub() 中,根据 endpoint.sampleResponseClosure() 来处理 sample 数据;


后面的流程就是数据返回了。

到这里,关于 Moya 的解析就结束,以后会把 AlamofireAFNetworking 这种网络请求库也好好研究一番。
iOSSwift

Alamofire 用法(一)

Moya 的实现(一)