阅读 220

Alamofire 学习(三)-Request 上篇

一、简介

前两篇《Alamofire 学习(一)-网络基础知识准备》《Alamofire 学习(二)-URLSession 知识准备》,我恶补了一下网络基础知识和 URLSession 的知识,有需要的朋友可以去看看。现在终于可以开始正式学习 Alamofire 了。

1、Alamofire 是什么

Alamofireswift 写的一个非常优秀的网络请求框架,相当于 OC 中的 AFNetWork,而且与 AF 是同一家出的,其实 AFNetwork 的前缀 AF 便是 Alamofire 的缩写。它本质是基于 URLSession 的封装,让我们网络请求相关代码更简洁易用。github 上截止现在已经 31.7k 颗⭐️了,附上 github 地址 Alamofire

2、Alamofire 功能特性

  • 链式的请求/响应方法
  • URL / JSON / plist 参数编码
  • 上传类型支持:文件( File)、数据( Data)、流( Stream)以及 MultipartFormData
  • 支持文件下载,下载支持断点续传
  • 支持使用 NSURLCredential 进行身份验证
  • HTTP 响应验证
  • TLS Certificate and Public Key Pinning
  • Progress Closure & NSProgress

二、SesssionManager

我们先来认识一个类 SesssionManagerSesssionManager 就是对外提供的管理者,这个管理者具备整个 Alamofire 的所有功能。

1、SesssionManager 的初始化

下面是 SesssionManager 初始化部分的源码:

public init(
    configuration: URLSessionConfiguration = URLSessionConfiguration.default,
    delegate: SessionDelegate = SessionDelegate(),
    serverTrustPolicyManager: ServerTrustPolicyManager? = nil)
{
    self.delegate = delegate
    self.session = URLSession(configuration: configuration, delegate: delegate, delegateQueue: nil)

    commonInit(serverTrustPolicyManager: serverTrustPolicyManager)
}
复制代码

可以看到它初始化了 session,其中 configuration 是默认了 .default 的模式,并将 代理移交,通过创建 SessionDelegate 这个专门处理代理的类来实现 URLSession 的代理。

2、代理完成回调

open func urlSessionDidFinishEvents(forBackgroundURLSession session: URLSession) {
    sessionDidFinishEventsForBackgroundURLSession?(session)
}
复制代码

三、后台下载

上篇讲了 URLSession 的后台下载,现在再来看一下 Alamofire 的后台下载吧。 我先封装了一个后台下载管理类的单例 , MYBackgroundManager

struct MYBackgroundManager {
    static let shared = MYBackgroundManager()
    let manager: SessionManager = {
        let configuration = URLSessionConfiguration.background(withIdentifier: "com.lgcooci.AlamofireTest.demo")
        configuration.httpAdditionalHeaders = SessionManager.defaultHTTPHeaders
        configuration.timeoutIntervalForRequest = 10
        configuration.timeoutIntervalForResource = 10        

        return SessionManager(configuration: configuration)
    }()
}
复制代码

⚠️注意: 1、设为.background模式: 这里的 configuration 一定要设置为 .background 模式的(默认是**.default**),不然无法实现后台下载。

2、做成单例: 这里我把 manager 做成了单例,不然在进入后台就会释放,同时网络也就会报错:Error Domain=NSURLErrorDomain Code=-999 "cancelled",这样在 AppDelegate 的回调方便接收。

调用的时候很方便:

MYBackgroundManager.shared.manager
    .download(self.urlDownloadStr) { (url, response) -> (destinationURL: URL, options: DownloadRequest.DownloadOptions) in
    let documentUrl = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first
    let fileUrl     = documentUrl?.appendingPathComponent(response.suggestedFilename!)
    return (fileUrl!,[.removePreviousFile,.createIntermediateDirectories])
    }
    .response { (downloadResponse) in
        print("下载回调信息: \(downloadResponse)")
    }
    .downloadProgress { (progress) in
        print("下载进度 : \(progress)")
}
复制代码

AppDelegatehandleEventsForBackgroundURLSession 中用单例接收:

func application(_ application: UIApplication, handleEventsForBackgroundURLSession identifier: String, completionHandler: @escaping () -> Void) {
    MYBackgroundManager.shared.manager.backgroundCompletionHandler = completionHandler
}
复制代码

四、Request

Request 是一个父类,它有如下几个子类:

  • DataRequest
  • DownloadRequest
  • UploadRequest
  • StreamRequest 不同的 request 有不同的作用,各司其职。

1、使用

使用起来很简单,像下面这样就实现了一个简单的 get 请求

SessionManager.default.request(urlString, method: .get, parameters: ["username":"凡几多"])
    .response { (response) in
        debugPrint(response)
}
复制代码

2、源码解析

点击 request 进去,可以看到源码是下面这样的:

open func request(
    _ url: URLConvertible,
    method: HTTPMethod = .get,
    parameters: Parameters? = nil,
    encoding: ParameterEncoding = URLEncoding.default,
    headers: HTTPHeaders? = nil)
    -> DataRequest
{
    var originalRequest: URLRequest?
    
    do {
        originalRequest = try URLRequest(url: url, method: method, headers: headers)
        let encodedURLRequest = try encoding.encode(originalRequest!, with: parameters)
        return request(encodedURLRequest)
    } catch {
        return request(originalRequest, failedWith: error)
    }
}
复制代码

可以看到先是根据传进来的 url、methodheaders 创建了一个 URLRequest,然后对 parameters 进行 URLEncoding 编码,最后返回一个 DataRequest。 这里我们详细看一下 encoding.encode 里是如何编码的, 这里判断了 method 的类型,

if let method = HTTPMethod(rawValue: urlRequest.httpMethod ?? "GET"), encodesParametersInURL(with: method) {
复制代码

(1)get 请求

如果是直接拼接到 url 后面的 method 类型(如 get),假设我们的请求为 get 请求,则执行下面的代码:

let percentEncodedQuery = (urlComponents.percentEncodedQuery.map { $0 + "&" } ?? "") + query(parameters)
                urlComponents.percentEncodedQuery = percentEncodedQuery
                urlRequest.url = urlComponents.url
复制代码

假设我们的 get 请求为:

http://www.douban.com/j/app/radio/channels?username="fanjiduo"&&password="123456"
复制代码

那么它的 urlComponents 是这样的:

http://www.douban.com/j/app/radio/channels
  - scheme : "http"
  - host : "www.douban.com"
  - path : "/j/app/radio/channels"
复制代码

我们发现它是把路由部分 urlComponents 进行了百分号编码:

 (urlComponents.percentEncodedQuery.map { $0 + "&" } ?? "")
复制代码

然后对参数部分 parameters 也进行了百分号编码:

query(parameters)
复制代码

我们点击 query 进去看一下它的具体实现

private func query(_ parameters: [String: Any]) -> String {
    var components: [(String, String)] = []
    
    for key in parameters.keys.sorted(by: <) {
        let value = parameters[key]!
        components += queryComponents(fromKey: key, value: value)
    }
    return components.map { "\($0)=\($1)" }.joined(separator: "&")
}
复制代码

1、先把参数根据 ASCII 进行了升序排序并遍历:

for key in parameters.keys.sorted(by: <) {
    let value = parameters[key]!
复制代码

2、把键值对通过 queryComponents 函数进行递归并进行百分号编码,然后返回一个元祖,再把元祖添加到了数组 components 中

components += queryComponents(fromKey: key, value: value)
复制代码

3、把元祖中的第一个元素和第二个元素用 = 连接,然后再用 & 符号进行了分割

return components.map { "\($0)=\($1)" }.joined(separator: "&")
复制代码

(2)post 请求

如果是 post 请求,那么 encoding.encode 又是如何编码的呢? post 请求会在请求头中多加一个 Content-Type

if urlRequest.value(forHTTPHeaderField: "Content-Type") == nil {
                urlRequest.setValue("application/x-www-form-urlencoded; charset=utf-8", forHTTPHeaderField: "Content-Type")
            } 
复制代码

参数则不再放到请求头里了,而是放在 urlRequest.httpBody中,并且进行了 .data 处理:

urlRequest.httpBody = query(parameters).data(using: .utf8, allowLossyConversion: false)
复制代码

3、task 和 request 的关系

接下来我们分析一下内部:url -> request -> task 的过程。这个过程中还建立了 task 以及 request 之间的绑定关系。 继续上面的源码分析:

return request(encodedURLRequest)
复制代码

request 进去看一下源码:

open func request(_ urlRequest: URLRequestConvertible) -> DataRequest {
    var originalRequest: URLRequest?
    
    do {
        originalRequest = try urlRequest.asURLRequest()
        let originalTask = DataRequest.Requestable(urlRequest: originalRequest!)
        
        let task = try originalTask.task(session: session, adapter: adapter, queue: queue)
        let request = DataRequest(session: session, requestTask: .data(originalTask, task))
        
        delegate[task] = request
        
        if startRequestsImmediately { request.resume() }
        
        return request
    } catch {
        return request(originalRequest, failedWith: error)
    }
}
复制代码

我们看到这行代码:

let originalTask = DataRequest.Requestable(urlRequest: originalRequest!)
复制代码

点击 Requestable 进去:

struct Requestable: TaskConvertible {
    let urlRequest: URLRequest
    
    func task(session: URLSession, adapter: RequestAdapter?, queue: DispatchQueue) throws -> URLSessionTask {
        do {
            let urlRequest = try self.urlRequest.adapt(using: adapter)
            return queue.sync { session.dataTask(with: urlRequest) }
        } catch {
            throw AdaptError(error: error)
        }
    }
}
复制代码

我们发现 Requestable 其实是一个结构体,帮助 DataRequest 创建了一个包含 task 的结构体对象,相当于 DataRequest 的一个助理,帮 Requestable 创建了 task。 之所以这样写,而不直接把 task 创建写在 DataRequest 中,是为了降低耦合性。

  • 初始化 request 通过助理 Requestable 得到结构体 originalTask 以后,就可以得到下面的 task,并且初始化出 request 了:
let task = try originalTask.task(session: session, adapter: adapter, queue: queue)
let request = DataRequest(session: session, requestTask: .data(originalTask, task))
复制代码

这个初始化方法是写在父类 Request 里的,

init(session: URLSession, requestTask: RequestTask, error: Error? = nil) {
    self.session = session
    
    switch requestTask {
    case .data(let originalTask, let task):
        taskDelegate = DataTaskDelegate(task: task)
        self.originalTask = originalTask
    case .download(let originalTask, let task):
        taskDelegate = DownloadTaskDelegate(task: task)
        self.originalTask = originalTask
    case .upload(let originalTask, let task):
        taskDelegate = UploadTaskDelegate(task: task)
        self.originalTask = originalTask
    case .stream(let originalTask, let task):
        taskDelegate = TaskDelegate(task: task)
        self.originalTask = originalTask
    }
    
    delegate.error = error
    delegate.queue.addOperation { self.endTime = CFAbsoluteTimeGetCurrent() }
}
复制代码

可以看出这里传递不同的枚举值,初始化不同的 taskdelegate

  • 绑定 taskrequest 下面的代码对 taskrequest 进行了绑定。方便在 SessionDelegate 下发任务,task 直接检索,request 方便直接获取。
delegate[task] = request
复制代码

这一篇对 Request 有了大概的了解,下一篇我们继续。

转载请备注原文出处,不得用于商业传播——凡几多

关注下面的标签,发现更多相似文章
评论